mongodb 6.5.0-dev.20240424.sha.6abc074 → 6.5.0-dev.20240502.sha.9d73f45

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 (36) hide show
  1. package/lib/cmap/connection.js +8 -4
  2. package/lib/cmap/connection.js.map +1 -1
  3. package/lib/cmap/wire_protocol/on_demand/document.js +27 -16
  4. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  5. package/lib/cmap/wire_protocol/responses.js +106 -10
  6. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  7. package/lib/cursor/abstract_cursor.js +33 -27
  8. package/lib/cursor/abstract_cursor.js.map +1 -1
  9. package/lib/cursor/find_cursor.js +14 -8
  10. package/lib/cursor/find_cursor.js.map +1 -1
  11. package/lib/cursor/run_command_cursor.js +2 -1
  12. package/lib/cursor/run_command_cursor.js.map +1 -1
  13. package/lib/index.js.map +1 -1
  14. package/lib/operations/execute_operation.js.map +1 -1
  15. package/lib/operations/find.js +4 -3
  16. package/lib/operations/find.js.map +1 -1
  17. package/lib/operations/get_more.js +2 -1
  18. package/lib/operations/get_more.js.map +1 -1
  19. package/lib/sdam/server.js +3 -7
  20. package/lib/sdam/server.js.map +1 -1
  21. package/lib/utils.js +13 -2
  22. package/lib/utils.js.map +1 -1
  23. package/mongodb.d.ts +2 -0
  24. package/package.json +1 -1
  25. package/src/cmap/connection.ts +14 -5
  26. package/src/cmap/wire_protocol/on_demand/document.ts +36 -21
  27. package/src/cmap/wire_protocol/responses.ts +139 -11
  28. package/src/cursor/abstract_cursor.ts +61 -21
  29. package/src/cursor/find_cursor.ts +12 -9
  30. package/src/cursor/run_command_cursor.ts +2 -1
  31. package/src/index.ts +5 -1
  32. package/src/operations/execute_operation.ts +2 -1
  33. package/src/operations/find.ts +14 -14
  34. package/src/operations/get_more.ts +9 -1
  35. package/src/sdam/server.ts +22 -7
  36. package/src/utils.ts +13 -0
@@ -58,7 +58,7 @@ export class OnDemandDocument {
58
58
  private readonly indexFound: Record<number, boolean> = Object.create(null);
59
59
 
60
60
  /** All bson elements in this document */
61
- private readonly elements: BSONElement[];
61
+ private readonly elements: ReadonlyArray<BSONElement>;
62
62
 
63
63
  constructor(
64
64
  /** BSON bytes, this document begins at offset */
@@ -97,7 +97,7 @@ export class OnDemandDocument {
97
97
  * @param name - a basic latin string name of a BSON element
98
98
  * @returns
99
99
  */
100
- private getElement(name: string): CachedBSONElement | null {
100
+ private getElement(name: string | number): CachedBSONElement | null {
101
101
  const cachedElement = this.cache[name];
102
102
  if (cachedElement === false) return null;
103
103
 
@@ -105,6 +105,22 @@ export class OnDemandDocument {
105
105
  return cachedElement;
106
106
  }
107
107
 
108
+ if (typeof name === 'number') {
109
+ if (this.isArray) {
110
+ if (name < this.elements.length) {
111
+ const element = this.elements[name];
112
+ const cachedElement = { element, value: undefined };
113
+ this.cache[name] = cachedElement;
114
+ this.indexFound[name] = true;
115
+ return cachedElement;
116
+ } else {
117
+ return null;
118
+ }
119
+ } else {
120
+ return null;
121
+ }
122
+ }
123
+
108
124
  for (let index = 0; index < this.elements.length; index++) {
109
125
  const element = this.elements[index];
110
126
 
@@ -197,6 +213,13 @@ export class OnDemandDocument {
197
213
  }
198
214
  }
199
215
 
216
+ /**
217
+ * Returns the number of elements in this BSON document
218
+ */
219
+ public size() {
220
+ return this.elements.length;
221
+ }
222
+
200
223
  /**
201
224
  * Checks for the existence of an element by name.
202
225
  *
@@ -222,16 +245,20 @@ export class OnDemandDocument {
222
245
  * @param required - whether or not the element is expected to exist, if true this function will throw if it is not present
223
246
  */
224
247
  public get<const T extends keyof JSTypeOf>(
225
- name: string,
248
+ name: string | number,
226
249
  as: T,
227
250
  required?: false | undefined
228
251
  ): JSTypeOf[T] | null;
229
252
 
230
253
  /** `required` will make `get` throw if name does not exist or is null/undefined */
231
- public get<const T extends keyof JSTypeOf>(name: string, as: T, required: true): JSTypeOf[T];
254
+ public get<const T extends keyof JSTypeOf>(
255
+ name: string | number,
256
+ as: T,
257
+ required: true
258
+ ): JSTypeOf[T];
232
259
 
233
260
  public get<const T extends keyof JSTypeOf>(
234
- name: string,
261
+ name: string | number,
235
262
  as: T,
236
263
  required?: boolean
237
264
  ): JSTypeOf[T] | null {
@@ -303,21 +330,9 @@ export class OnDemandDocument {
303
330
  });
304
331
  }
305
332
 
306
- /**
307
- * Iterates through the elements of a document reviving them using the `as` BSONType.
308
- *
309
- * @param as - The type to revive all elements as
310
- */
311
- public *valuesAs<const T extends keyof JSTypeOf>(as: T): Generator<JSTypeOf[T]> {
312
- if (!this.isArray) {
313
- throw new BSONError('Unexpected conversion of non-array value to array');
314
- }
315
- let counter = 0;
316
- for (const element of this.elements) {
317
- const value = this.toJSValue<T>(element, as);
318
- this.cache[counter] = { element, value };
319
- yield value;
320
- counter += 1;
321
- }
333
+ /** Returns this document's bytes only */
334
+ toBytes() {
335
+ const size = getInt32LE(this.bson, this.offset);
336
+ return this.bson.subarray(this.offset, this.offset + size);
322
337
  }
323
338
  }
@@ -1,7 +1,62 @@
1
- import { type BSONSerializeOptions, BSONType, type Document, type Timestamp } from '../../bson';
1
+ import {
2
+ type BSONSerializeOptions,
3
+ BSONType,
4
+ type Document,
5
+ Long,
6
+ parseToElementsToArray,
7
+ type Timestamp
8
+ } from '../../bson';
9
+ import { MongoUnexpectedServerResponseError } from '../../error';
2
10
  import { type ClusterTime } from '../../sdam/common';
11
+ import { type MongoDBNamespace, ns } from '../../utils';
3
12
  import { OnDemandDocument } from './on_demand/document';
4
13
 
14
+ // eslint-disable-next-line no-restricted-syntax
15
+ const enum BSONElementOffset {
16
+ type = 0,
17
+ nameOffset = 1,
18
+ nameLength = 2,
19
+ offset = 3,
20
+ length = 4
21
+ }
22
+ /**
23
+ * Accepts a BSON payload and checks for na "ok: 0" element.
24
+ * This utility is intended to prevent calling response class constructors
25
+ * that expect the result to be a success and demand certain properties to exist.
26
+ *
27
+ * For example, a cursor response always expects a cursor embedded document.
28
+ * In order to write the class such that the properties reflect that assertion (non-null)
29
+ * we cannot invoke the subclass constructor if the BSON represents an error.
30
+ *
31
+ * @param bytes - BSON document returned from the server
32
+ */
33
+ export function isErrorResponse(bson: Uint8Array): boolean {
34
+ const elements = parseToElementsToArray(bson, 0);
35
+ for (let eIdx = 0; eIdx < elements.length; eIdx++) {
36
+ const element = elements[eIdx];
37
+
38
+ if (element[BSONElementOffset.nameLength] === 2) {
39
+ const nameOffset = element[BSONElementOffset.nameOffset];
40
+
41
+ // 111 == "o", 107 == "k"
42
+ if (bson[nameOffset] === 111 && bson[nameOffset + 1] === 107) {
43
+ const valueOffset = element[BSONElementOffset.offset];
44
+ const valueLength = element[BSONElementOffset.length];
45
+
46
+ // If any byte in the length of the ok number (works for any type) is non zero,
47
+ // then it is considered "ok: 1"
48
+ for (let i = valueOffset; i < valueOffset + valueLength; i++) {
49
+ if (bson[i] !== 0x00) return false;
50
+ }
51
+
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+
57
+ return true;
58
+ }
59
+
5
60
  /** @internal */
6
61
  export type MongoDBResponseConstructor = {
7
62
  new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
@@ -9,6 +64,10 @@ export type MongoDBResponseConstructor = {
9
64
 
10
65
  /** @internal */
11
66
  export class MongoDBResponse extends OnDemandDocument {
67
+ static is(value: unknown): value is MongoDBResponse {
68
+ return value instanceof MongoDBResponse;
69
+ }
70
+
12
71
  // {ok:1}
13
72
  static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
14
73
 
@@ -83,27 +142,96 @@ export class MongoDBResponse extends OnDemandDocument {
83
142
  return this.clusterTime ?? null;
84
143
  }
85
144
 
86
- public override toObject(options: BSONSerializeOptions = {}): Record<string, any> {
145
+ public override toObject(options?: BSONSerializeOptions): Record<string, any> {
87
146
  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 ?? {},
147
+ useBigInt64: options?.useBigInt64,
148
+ promoteLongs: options?.promoteLongs,
149
+ promoteValues: options?.promoteValues,
150
+ promoteBuffers: options?.promoteBuffers,
151
+ bsonRegExp: options?.bsonRegExp,
152
+ raw: options?.raw ?? false,
153
+ fieldsAsRaw: options?.fieldsAsRaw ?? {},
95
154
  validation: this.parseBsonSerializationOptions(options)
96
155
  };
97
156
  return super.toObject(exactBSONOptions);
98
157
  }
99
158
 
100
- private parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
159
+ private parseBsonSerializationOptions(options?: { enableUtf8Validation?: boolean }): {
101
160
  utf8: { writeErrors: false } | false;
102
161
  } {
162
+ const enableUtf8Validation = options?.enableUtf8Validation;
103
163
  if (enableUtf8Validation === false) {
104
164
  return { utf8: false };
105
165
  }
106
-
107
166
  return { utf8: { writeErrors: false } };
108
167
  }
109
168
  }
169
+
170
+ /** @internal */
171
+ export class CursorResponse extends MongoDBResponse {
172
+ /**
173
+ * This supports a feature of the FindCursor.
174
+ * It is an optimization to avoid an extra getMore when the limit has been reached
175
+ */
176
+ static emptyGetMore = { id: new Long(0), length: 0, shift: () => null };
177
+
178
+ static override is(value: unknown): value is CursorResponse {
179
+ return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;
180
+ }
181
+
182
+ public id: Long;
183
+ public ns: MongoDBNamespace | null = null;
184
+ public batchSize = 0;
185
+
186
+ private batch: OnDemandDocument;
187
+ private iterated = 0;
188
+
189
+ constructor(bytes: Uint8Array, offset?: number, isArray?: boolean) {
190
+ super(bytes, offset, isArray);
191
+
192
+ const cursor = this.get('cursor', BSONType.object, true);
193
+
194
+ const id = cursor.get('id', BSONType.long, true);
195
+ this.id = new Long(Number(id & 0xffff_ffffn), Number((id >> 32n) & 0xffff_ffffn));
196
+
197
+ const namespace = cursor.get('ns', BSONType.string);
198
+ if (namespace != null) this.ns = ns(namespace);
199
+
200
+ if (cursor.has('firstBatch')) this.batch = cursor.get('firstBatch', BSONType.array, true);
201
+ else if (cursor.has('nextBatch')) this.batch = cursor.get('nextBatch', BSONType.array, true);
202
+ else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
203
+
204
+ this.batchSize = this.batch.size();
205
+ }
206
+
207
+ get length() {
208
+ return Math.max(this.batchSize - this.iterated, 0);
209
+ }
210
+
211
+ shift(options?: BSONSerializeOptions): any {
212
+ if (this.iterated >= this.batchSize) {
213
+ return null;
214
+ }
215
+
216
+ const result = this.batch.get(this.iterated, BSONType.object, true) ?? null;
217
+ this.iterated += 1;
218
+
219
+ if (options?.raw) {
220
+ return result.toBytes();
221
+ } else {
222
+ return result.toObject(options);
223
+ }
224
+ }
225
+
226
+ clear() {
227
+ this.iterated = this.batchSize;
228
+ }
229
+
230
+ pushMany() {
231
+ throw new Error('pushMany Unsupported method');
232
+ }
233
+
234
+ push() {
235
+ throw new Error('push Unsupported method');
236
+ }
237
+ }
@@ -1,6 +1,7 @@
1
1
  import { Readable, Transform } from 'stream';
2
2
 
3
3
  import { type BSONSerializeOptions, type Document, Long, pluckBSONSerializeOptions } from '../bson';
4
+ import { CursorResponse } from '../cmap/wire_protocol/responses';
4
5
  import {
5
6
  type AnyError,
6
7
  MongoAPIError,
@@ -144,7 +145,13 @@ export abstract class AbstractCursor<
144
145
  /** @internal */
145
146
  [kNamespace]: MongoDBNamespace;
146
147
  /** @internal */
147
- [kDocuments]: List<TSchema>;
148
+ [kDocuments]: {
149
+ length: number;
150
+ shift(bsonOptions?: any): TSchema | null;
151
+ clear(): void;
152
+ pushMany(many: Iterable<TSchema>): void;
153
+ push(item: TSchema): void;
154
+ };
148
155
  /** @internal */
149
156
  [kClient]: MongoClient;
150
157
  /** @internal */
@@ -286,7 +293,7 @@ export abstract class AbstractCursor<
286
293
  const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length);
287
294
 
288
295
  for (let count = 0; count < documentsToRead; count++) {
289
- const document = this[kDocuments].shift();
296
+ const document = this[kDocuments].shift(this[kOptions]);
290
297
  if (document != null) {
291
298
  bufferedDocs.push(document);
292
299
  }
@@ -382,14 +389,7 @@ export abstract class AbstractCursor<
382
389
  return true;
383
390
  }
384
391
 
385
- const doc = await next<TSchema>(this, { blocking: true, transform: false });
386
-
387
- if (doc) {
388
- this[kDocuments].unshift(doc);
389
- return true;
390
- }
391
-
392
- return false;
392
+ return await next(this, { blocking: true, transform: false, shift: false });
393
393
  }
394
394
 
395
395
  /** Get the next available document from the cursor, returns null if no more documents are available. */
@@ -398,7 +398,7 @@ export abstract class AbstractCursor<
398
398
  throw new MongoCursorExhaustedError();
399
399
  }
400
400
 
401
- return await next(this, { blocking: true, transform: true });
401
+ return await next(this, { blocking: true, transform: true, shift: true });
402
402
  }
403
403
 
404
404
  /**
@@ -409,7 +409,7 @@ export abstract class AbstractCursor<
409
409
  throw new MongoCursorExhaustedError();
410
410
  }
411
411
 
412
- return await next(this, { blocking: false, transform: true });
412
+ return await next(this, { blocking: false, transform: true, shift: true });
413
413
  }
414
414
 
415
415
  /**
@@ -633,12 +633,13 @@ export abstract class AbstractCursor<
633
633
  protected abstract _initialize(session: ClientSession | undefined): Promise<ExecutionResult>;
634
634
 
635
635
  /** @internal */
636
- async getMore(batchSize: number): Promise<Document | null> {
636
+ async getMore(batchSize: number, useCursorResponse = false): Promise<Document | null> {
637
637
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
638
638
  const getMoreOperation = new GetMoreOperation(this[kNamespace], this[kId]!, this[kServer]!, {
639
639
  ...this[kOptions],
640
640
  session: this[kSession],
641
- batchSize
641
+ batchSize,
642
+ useCursorResponse
642
643
  });
643
644
 
644
645
  return await executeOperation(this[kClient], getMoreOperation);
@@ -656,7 +657,11 @@ export abstract class AbstractCursor<
656
657
  const state = await this._initialize(this[kSession]);
657
658
  const response = state.response;
658
659
  this[kServer] = state.server;
659
- if (response.cursor) {
660
+ if (CursorResponse.is(response)) {
661
+ this[kId] = response.id;
662
+ if (response.ns) this[kNamespace] = response.ns;
663
+ this[kDocuments] = response;
664
+ } else if (response.cursor) {
660
665
  // TODO(NODE-2674): Preserve int64 sent from MongoDB
661
666
  this[kId] =
662
667
  typeof response.cursor.id === 'number'
@@ -713,13 +718,42 @@ async function next<T>(
713
718
  cursor: AbstractCursor<T>,
714
719
  {
715
720
  blocking,
716
- transform
721
+ transform,
722
+ shift
717
723
  }: {
718
724
  blocking: boolean;
719
725
  transform: boolean;
726
+ shift: false;
720
727
  }
721
- ): Promise<T | null> {
728
+ ): Promise<boolean>;
729
+
730
+ async function next<T>(
731
+ cursor: AbstractCursor<T>,
732
+ {
733
+ blocking,
734
+ transform,
735
+ shift
736
+ }: {
737
+ blocking: boolean;
738
+ transform: boolean;
739
+ shift: true;
740
+ }
741
+ ): Promise<T | null>;
742
+
743
+ async function next<T>(
744
+ cursor: AbstractCursor<T>,
745
+ {
746
+ blocking,
747
+ transform,
748
+ shift
749
+ }: {
750
+ blocking: boolean;
751
+ transform: boolean;
752
+ shift: boolean;
753
+ }
754
+ ): Promise<boolean | T | null> {
722
755
  if (cursor.closed) {
756
+ if (!shift) return false;
723
757
  return null;
724
758
  }
725
759
 
@@ -730,7 +764,8 @@ async function next<T>(
730
764
  }
731
765
 
732
766
  if (cursor[kDocuments].length !== 0) {
733
- const doc = cursor[kDocuments].shift();
767
+ if (!shift) return true;
768
+ const doc = cursor[kDocuments].shift(cursor[kOptions]);
734
769
 
735
770
  if (doc != null && transform && cursor[kTransform]) {
736
771
  try {
@@ -754,6 +789,7 @@ async function next<T>(
754
789
  // cleanupCursor should never throw, but if it does it indicates a bug in the driver
755
790
  // and we should surface the error
756
791
  await cleanupCursor(cursor, {});
792
+ if (!shift) return false;
757
793
  return null;
758
794
  }
759
795
 
@@ -762,8 +798,10 @@ async function next<T>(
762
798
 
763
799
  try {
764
800
  const response = await cursor.getMore(batchSize);
765
-
766
- if (response) {
801
+ if (CursorResponse.is(response)) {
802
+ cursor[kId] = response.id;
803
+ cursor[kDocuments] = response;
804
+ } else if (response) {
767
805
  const cursorId =
768
806
  typeof response.cursor.id === 'number'
769
807
  ? Long.fromNumber(response.cursor.id)
@@ -796,10 +834,12 @@ async function next<T>(
796
834
  }
797
835
 
798
836
  if (cursor[kDocuments].length === 0 && blocking === false) {
837
+ if (!shift) return false;
799
838
  return null;
800
839
  }
801
840
  } while (!cursor.isDead || cursor[kDocuments].length !== 0);
802
841
 
842
+ if (!shift) return false;
803
843
  return null;
804
844
  }
805
845
 
@@ -921,7 +961,7 @@ class ReadableCursorStream extends Readable {
921
961
 
922
962
  private _readNext() {
923
963
  // eslint-disable-next-line github/no-then
924
- next(this._cursor, { blocking: true, transform: true }).then(
964
+ next(this._cursor, { blocking: true, transform: true, shift: true }).then(
925
965
  result => {
926
966
  if (result == null) {
927
967
  this.push(null);
@@ -1,4 +1,5 @@
1
- import { type Document, Long } from '../bson';
1
+ import { type Document } from '../bson';
2
+ import { CursorResponse } from '../cmap/wire_protocol/responses';
2
3
  import { MongoInvalidArgumentError, MongoTailableCursorError } from '../error';
3
4
  import { type ExplainVerbosityLike } from '../explain';
4
5
  import type { MongoClient } from '../mongo_client';
@@ -34,7 +35,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
34
35
  /** @internal */
35
36
  [kFilter]: Document;
36
37
  /** @internal */
37
- [kNumReturned]?: number;
38
+ [kNumReturned] = 0;
38
39
  /** @internal */
39
40
  [kBuiltOptions]: FindOptions;
40
41
 
@@ -69,7 +70,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
69
70
 
70
71
  /** @internal */
71
72
  async _initialize(session: ClientSession): Promise<ExecutionResult> {
72
- const findOperation = new FindOperation(undefined, this.namespace, this[kFilter], {
73
+ const findOperation = new FindOperation(this.namespace, this[kFilter], {
73
74
  ...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
74
75
  ...this.cursorOptions,
75
76
  session
@@ -78,7 +79,9 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
78
79
  const response = await executeOperation(this.client, findOperation);
79
80
 
80
81
  // the response is not a cursor when `explain` is enabled
81
- this[kNumReturned] = response.cursor?.firstBatch?.length;
82
+ if (CursorResponse.is(response)) {
83
+ this[kNumReturned] = response.batchSize;
84
+ }
82
85
 
83
86
  // TODO: NODE-2882
84
87
  return { server: findOperation.server, session, response };
@@ -107,14 +110,14 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
107
110
  // instead, if we determine there are no more documents to request from the server, we preemptively
108
111
  // close the cursor
109
112
  }
110
- return { cursor: { id: Long.ZERO, nextBatch: [] } };
113
+ return CursorResponse.emptyGetMore;
111
114
  }
112
115
  }
113
116
 
114
- const response = await super.getMore(batchSize);
117
+ const response = await super.getMore(batchSize, this.client.autoEncrypter ? false : true);
115
118
  // TODO: wrap this in some logic to prevent it from happening if we don't need this support
116
- if (response) {
117
- this[kNumReturned] = this[kNumReturned] + response.cursor.nextBatch.length;
119
+ if (CursorResponse.is(response)) {
120
+ this[kNumReturned] = this[kNumReturned] + response.batchSize;
118
121
  }
119
122
 
120
123
  return response;
@@ -145,7 +148,7 @@ export class FindCursor<TSchema = any> extends AbstractCursor<TSchema> {
145
148
  async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
146
149
  return await executeOperation(
147
150
  this.client,
148
- new FindOperation(undefined, this.namespace, this[kFilter], {
151
+ new FindOperation(this.namespace, this[kFilter], {
149
152
  ...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
150
153
  ...this.cursorOptions,
151
154
  explain: verbosity ?? true
@@ -125,7 +125,8 @@ export class RunCommandCursor extends AbstractCursor {
125
125
  const getMoreOperation = new GetMoreOperation(this.namespace, this.id!, this.server!, {
126
126
  ...this.cursorOptions,
127
127
  session: this.session,
128
- ...this.getMoreOptions
128
+ ...this.getMoreOptions,
129
+ useCursorResponse: false
129
130
  });
130
131
 
131
132
  return await executeOperation(this.client, getMoreOperation);
package/src/index.ts CHANGED
@@ -290,7 +290,11 @@ export type { ConnectionPoolMetrics } from './cmap/metrics';
290
290
  export type { StreamDescription, StreamDescriptionOptions } from './cmap/stream_description';
291
291
  export type { CompressorName } from './cmap/wire_protocol/compression';
292
292
  export type { JSTypeOf, OnDemandDocument } from './cmap/wire_protocol/on_demand/document';
293
- export type { MongoDBResponse, MongoDBResponseConstructor } from './cmap/wire_protocol/responses';
293
+ export type {
294
+ CursorResponse,
295
+ MongoDBResponse,
296
+ MongoDBResponseConstructor
297
+ } from './cmap/wire_protocol/responses';
294
298
  export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection';
295
299
  export type {
296
300
  COMMAND_FAILED,
@@ -1,4 +1,5 @@
1
1
  import type { Document } from '../bson';
2
+ import { type CursorResponse } from '../cmap/wire_protocol/responses';
2
3
  import {
3
4
  isRetryableReadError,
4
5
  isRetryableWriteError,
@@ -44,7 +45,7 @@ export interface ExecutionResult {
44
45
  /** The session used for this operation, may be implicitly created */
45
46
  session?: ClientSession;
46
47
  /** The raw server response for the operation */
47
- response: Document;
48
+ response: Document | CursorResponse;
48
49
  }
49
50
 
50
51
  /**
@@ -1,5 +1,5 @@
1
1
  import type { Document } from '../bson';
2
- import type { Collection } from '../collection';
2
+ import { CursorResponse } from '../cmap/wire_protocol/responses';
3
3
  import { MongoInvalidArgumentError } from '../error';
4
4
  import { ReadConcern } from '../read_concern';
5
5
  import type { Server } from '../sdam/server';
@@ -77,13 +77,8 @@ export class FindOperation extends CommandOperation<Document> {
77
77
  override options: FindOptions & { writeConcern?: never };
78
78
  filter: Document;
79
79
 
80
- constructor(
81
- collection: Collection | undefined,
82
- ns: MongoDBNamespace,
83
- filter: Document = {},
84
- options: FindOptions = {}
85
- ) {
86
- super(collection, options);
80
+ constructor(ns: MongoDBNamespace, filter: Document = {}, options: FindOptions = {}) {
81
+ super(undefined, options);
87
82
 
88
83
  this.options = { ...options };
89
84
  delete this.options.writeConcern;
@@ -111,12 +106,17 @@ export class FindOperation extends CommandOperation<Document> {
111
106
  findCommand = decorateWithExplain(findCommand, this.explain);
112
107
  }
113
108
 
114
- return await server.command(this.ns, findCommand, {
115
- ...this.options,
116
- ...this.bsonOptions,
117
- documentsReturnedIn: 'firstBatch',
118
- session
119
- });
109
+ return await server.command(
110
+ this.ns,
111
+ findCommand,
112
+ {
113
+ ...this.options,
114
+ ...this.bsonOptions,
115
+ documentsReturnedIn: 'firstBatch',
116
+ session
117
+ },
118
+ this.explain ? undefined : CursorResponse
119
+ );
120
120
  }
121
121
  }
122
122
 
@@ -1,4 +1,5 @@
1
1
  import type { Document, Long } from '../bson';
2
+ import { CursorResponse } from '../cmap/wire_protocol/responses';
2
3
  import { MongoRuntimeError } from '../error';
3
4
  import type { Server } from '../sdam/server';
4
5
  import type { ClientSession } from '../sessions';
@@ -19,6 +20,8 @@ export interface GetMoreOptions extends OperationOptions {
19
20
  maxTimeMS?: number;
20
21
  /** TODO(NODE-4413): Address bug with maxAwaitTimeMS not being passed in from the cursor correctly */
21
22
  maxAwaitTimeMS?: number;
23
+
24
+ useCursorResponse: boolean;
22
25
  }
23
26
 
24
27
  /**
@@ -96,7 +99,12 @@ export class GetMoreOperation extends AbstractOperation {
96
99
  ...this.options
97
100
  };
98
101
 
99
- return await server.command(this.ns, getMoreCmd, commandOptions);
102
+ return await server.command(
103
+ this.ns,
104
+ getMoreCmd,
105
+ commandOptions,
106
+ this.options.useCursorResponse ? CursorResponse : undefined
107
+ );
100
108
  }
101
109
  }
102
110