mongodb 6.7.0-dev.20240608.sha.0655c730 → 6.7.0-dev.20240614.sha.3ed6a2ad

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 (96) hide show
  1. package/lib/bson.js.map +1 -1
  2. package/lib/client-side-encryption/auto_encrypter.js +8 -61
  3. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  4. package/lib/client-side-encryption/client_encryption.js +5 -5
  5. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  6. package/lib/client-side-encryption/providers/index.js.map +1 -1
  7. package/lib/client-side-encryption/state_machine.js +15 -11
  8. package/lib/client-side-encryption/state_machine.js.map +1 -1
  9. package/lib/cmap/connection.js +22 -20
  10. package/lib/cmap/connection.js.map +1 -1
  11. package/lib/cmap/wire_protocol/on_demand/document.js +8 -5
  12. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  13. package/lib/cmap/wire_protocol/responses.js +116 -40
  14. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  15. package/lib/constants.js +9 -1
  16. package/lib/constants.js.map +1 -1
  17. package/lib/cursor/abstract_cursor.js +24 -60
  18. package/lib/cursor/abstract_cursor.js.map +1 -1
  19. package/lib/cursor/aggregation_cursor.js +2 -3
  20. package/lib/cursor/aggregation_cursor.js.map +1 -1
  21. package/lib/cursor/change_stream_cursor.js +6 -8
  22. package/lib/cursor/change_stream_cursor.js.map +1 -1
  23. package/lib/cursor/find_cursor.js +5 -17
  24. package/lib/cursor/find_cursor.js.map +1 -1
  25. package/lib/cursor/list_collections_cursor.js +0 -1
  26. package/lib/cursor/list_collections_cursor.js.map +1 -1
  27. package/lib/cursor/list_indexes_cursor.js +0 -1
  28. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  29. package/lib/cursor/run_command_cursor.js +4 -6
  30. package/lib/cursor/run_command_cursor.js.map +1 -1
  31. package/lib/error.js +6 -21
  32. package/lib/error.js.map +1 -1
  33. package/lib/index.js.map +1 -1
  34. package/lib/operations/aggregate.js +2 -2
  35. package/lib/operations/aggregate.js.map +1 -1
  36. package/lib/operations/bulk_write.js +1 -2
  37. package/lib/operations/bulk_write.js.map +1 -1
  38. package/lib/operations/command.js +2 -3
  39. package/lib/operations/command.js.map +1 -1
  40. package/lib/operations/count_documents.js +1 -7
  41. package/lib/operations/count_documents.js.map +1 -1
  42. package/lib/operations/execute_operation.js.map +1 -1
  43. package/lib/operations/find.js +2 -1
  44. package/lib/operations/find.js.map +1 -1
  45. package/lib/operations/get_more.js +1 -1
  46. package/lib/operations/get_more.js.map +1 -1
  47. package/lib/operations/indexes.js +2 -1
  48. package/lib/operations/indexes.js.map +1 -1
  49. package/lib/operations/list_collections.js +2 -1
  50. package/lib/operations/list_collections.js.map +1 -1
  51. package/lib/operations/run_command.js +1 -1
  52. package/lib/operations/run_command.js.map +1 -1
  53. package/lib/operations/update.js +2 -1
  54. package/lib/operations/update.js.map +1 -1
  55. package/lib/sdam/server.js +7 -2
  56. package/lib/sdam/server.js.map +1 -1
  57. package/lib/utils.js +45 -1
  58. package/lib/utils.js.map +1 -1
  59. package/lib/write_concern.js +17 -1
  60. package/lib/write_concern.js.map +1 -1
  61. package/mongodb.d.ts +120 -85
  62. package/package.json +1 -1
  63. package/src/bson.ts +1 -0
  64. package/src/client-side-encryption/auto_encrypter.ts +9 -70
  65. package/src/client-side-encryption/client_encryption.ts +6 -6
  66. package/src/client-side-encryption/providers/index.ts +120 -92
  67. package/src/client-side-encryption/state_machine.ts +22 -18
  68. package/src/cmap/connection.ts +46 -50
  69. package/src/cmap/wire_protocol/on_demand/document.ts +13 -6
  70. package/src/cmap/wire_protocol/responses.ts +140 -45
  71. package/src/constants.ts +9 -0
  72. package/src/cursor/abstract_cursor.ts +51 -71
  73. package/src/cursor/aggregation_cursor.ts +13 -12
  74. package/src/cursor/change_stream_cursor.ts +20 -34
  75. package/src/cursor/find_cursor.ts +17 -25
  76. package/src/cursor/list_collections_cursor.ts +3 -4
  77. package/src/cursor/list_indexes_cursor.ts +3 -4
  78. package/src/cursor/run_command_cursor.ts +13 -19
  79. package/src/error.ts +16 -28
  80. package/src/index.ts +12 -8
  81. package/src/operations/aggregate.ts +12 -5
  82. package/src/operations/bulk_write.ts +1 -2
  83. package/src/operations/command.ts +17 -3
  84. package/src/operations/count_documents.ts +7 -11
  85. package/src/operations/delete.ts +2 -2
  86. package/src/operations/execute_operation.ts +0 -13
  87. package/src/operations/find.ts +7 -3
  88. package/src/operations/find_and_modify.ts +1 -1
  89. package/src/operations/get_more.ts +6 -10
  90. package/src/operations/indexes.ts +7 -3
  91. package/src/operations/list_collections.ts +8 -3
  92. package/src/operations/run_command.ts +16 -6
  93. package/src/operations/update.ts +2 -1
  94. package/src/sdam/server.ts +7 -2
  95. package/src/utils.ts +52 -2
  96. package/src/write_concern.ts +18 -0
@@ -1,15 +1,17 @@
1
1
  import {
2
+ type BSONElement,
2
3
  type BSONSerializeOptions,
3
4
  BSONType,
4
5
  type Document,
5
6
  Long,
6
7
  parseToElementsToArray,
8
+ pluckBSONSerializeOptions,
7
9
  type Timestamp
8
10
  } from '../../bson';
9
11
  import { MongoUnexpectedServerResponseError } from '../../error';
10
12
  import { type ClusterTime } from '../../sdam/common';
11
- import { type MongoDBNamespace, ns } from '../../utils';
12
- import { OnDemandDocument } from './on_demand/document';
13
+ import { decorateDecryptionResult, ns } from '../../utils';
14
+ import { type JSTypeOf, OnDemandDocument } from './on_demand/document';
13
15
 
14
16
  // eslint-disable-next-line no-restricted-syntax
15
17
  const enum BSONElementOffset {
@@ -30,8 +32,7 @@ const enum BSONElementOffset {
30
32
  *
31
33
  * @param bytes - BSON document returned from the server
32
34
  */
33
- export function isErrorResponse(bson: Uint8Array): boolean {
34
- const elements = parseToElementsToArray(bson, 0);
35
+ export function isErrorResponse(bson: Uint8Array, elements: BSONElement[]): boolean {
35
36
  for (let eIdx = 0; eIdx < elements.length; eIdx++) {
36
37
  const element = elements[eIdx];
37
38
 
@@ -60,26 +61,49 @@ export function isErrorResponse(bson: Uint8Array): boolean {
60
61
  /** @internal */
61
62
  export type MongoDBResponseConstructor = {
62
63
  new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
64
+ make(bson: Uint8Array): MongoDBResponse;
63
65
  };
64
66
 
65
67
  /** @internal */
66
68
  export class MongoDBResponse extends OnDemandDocument {
69
+ // Wrap error thrown from BSON
70
+ public override get<const T extends keyof JSTypeOf>(
71
+ name: string | number,
72
+ as: T,
73
+ required?: false | undefined
74
+ ): JSTypeOf[T] | null;
75
+ public override get<const T extends keyof JSTypeOf>(
76
+ name: string | number,
77
+ as: T,
78
+ required: true
79
+ ): JSTypeOf[T];
80
+ public override get<const T extends keyof JSTypeOf>(
81
+ name: string | number,
82
+ as: T,
83
+ required?: boolean | undefined
84
+ ): JSTypeOf[T] | null {
85
+ try {
86
+ return super.get(name, as, required);
87
+ } catch (cause) {
88
+ throw new MongoUnexpectedServerResponseError(cause.message, { cause });
89
+ }
90
+ }
91
+
67
92
  static is(value: unknown): value is MongoDBResponse {
68
93
  return value instanceof MongoDBResponse;
69
94
  }
70
95
 
96
+ static make(bson: Uint8Array) {
97
+ const elements = parseToElementsToArray(bson, 0);
98
+ const isError = isErrorResponse(bson, elements);
99
+ return isError
100
+ ? new MongoDBResponse(bson, 0, false, elements)
101
+ : new this(bson, 0, false, elements);
102
+ }
103
+
71
104
  // {ok:1}
72
105
  static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
73
106
 
74
- /** Indicates this document is a server error */
75
- public get isError() {
76
- let isError = this.ok === 0;
77
- isError ||= this.has('errmsg');
78
- isError ||= this.has('code');
79
- isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
80
- return isError;
81
- }
82
-
83
107
  /**
84
108
  * Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the
85
109
  * contents of the document.
@@ -110,6 +134,7 @@ export class MongoDBResponse extends OnDemandDocument {
110
134
  return this.get('operationTime', BSONType.timestamp);
111
135
  }
112
136
 
137
+ /** Normalizes whatever BSON value is "ok" to a JS number 1 or 0. */
113
138
  public get ok(): 0 | 1 {
114
139
  return this.getNumber('ok') ? 1 : 0;
115
140
  }
@@ -144,13 +169,7 @@ export class MongoDBResponse extends OnDemandDocument {
144
169
 
145
170
  public override toObject(options?: BSONSerializeOptions): Record<string, any> {
146
171
  const exactBSONOptions = {
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 ?? {},
172
+ ...pluckBSONSerializeOptions(options ?? {}),
154
173
  validation: this.parseBsonSerializationOptions(options)
155
174
  };
156
175
  return super.toObject(exactBSONOptions);
@@ -169,69 +188,145 @@ export class MongoDBResponse extends OnDemandDocument {
169
188
 
170
189
  /** @internal */
171
190
  export class CursorResponse extends MongoDBResponse {
191
+ /**
192
+ * Devtools need to know which keys were encrypted before the driver automatically decrypted them.
193
+ * If decorating is enabled (`Symbol.for('@@mdb.decorateDecryptionResult')`), this field will be set,
194
+ * storing the original encrypted response from the server, so that we can build an object that has
195
+ * the list of BSON keys that were encrypted stored at a well known symbol: `Symbol.for('@@mdb.decryptedKeys')`.
196
+ */
197
+ encryptedResponse?: MongoDBResponse;
172
198
  /**
173
199
  * This supports a feature of the FindCursor.
174
200
  * It is an optimization to avoid an extra getMore when the limit has been reached
175
201
  */
176
- static emptyGetMore = { id: new Long(0), length: 0, shift: () => null };
202
+ static emptyGetMore: CursorResponse = {
203
+ id: new Long(0),
204
+ length: 0,
205
+ shift: () => null
206
+ } as unknown as CursorResponse;
177
207
 
178
208
  static override is(value: unknown): value is CursorResponse {
179
209
  return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;
180
210
  }
181
211
 
182
- public id: Long;
183
- public ns: MongoDBNamespace | null = null;
184
- public batchSize = 0;
185
-
186
- private batch: OnDemandDocument;
212
+ private _batch: OnDemandDocument | null = null;
187
213
  private iterated = 0;
188
214
 
189
- constructor(bytes: Uint8Array, offset?: number, isArray?: boolean) {
190
- super(bytes, offset, isArray);
215
+ get cursor() {
216
+ return this.get('cursor', BSONType.object, true);
217
+ }
191
218
 
192
- const cursor = this.get('cursor', BSONType.object, true);
219
+ public get id(): Long {
220
+ try {
221
+ return Long.fromBigInt(this.cursor.get('id', BSONType.long, true));
222
+ } catch (cause) {
223
+ throw new MongoUnexpectedServerResponseError(cause.message, { cause });
224
+ }
225
+ }
193
226
 
194
- const id = cursor.get('id', BSONType.long, true);
195
- this.id = new Long(Number(id & 0xffff_ffffn), Number((id >> 32n) & 0xffff_ffffn));
227
+ public get ns() {
228
+ const namespace = this.cursor.get('ns', BSONType.string);
229
+ if (namespace != null) return ns(namespace);
230
+ return null;
231
+ }
232
+
233
+ public get length() {
234
+ return Math.max(this.batchSize - this.iterated, 0);
235
+ }
196
236
 
197
- const namespace = cursor.get('ns', BSONType.string);
198
- if (namespace != null) this.ns = ns(namespace);
237
+ private _encryptedBatch: OnDemandDocument | null = null;
238
+ get encryptedBatch() {
239
+ if (this.encryptedResponse == null) return null;
240
+ if (this._encryptedBatch != null) return this._encryptedBatch;
199
241
 
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);
242
+ const cursor = this.encryptedResponse?.get('cursor', BSONType.object);
243
+ if (cursor?.has('firstBatch'))
244
+ this._encryptedBatch = cursor.get('firstBatch', BSONType.array, true);
245
+ else if (cursor?.has('nextBatch'))
246
+ this._encryptedBatch = cursor.get('nextBatch', BSONType.array, true);
202
247
  else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
203
248
 
204
- this.batchSize = this.batch.size();
249
+ return this._encryptedBatch;
205
250
  }
206
251
 
207
- get length() {
208
- return Math.max(this.batchSize - this.iterated, 0);
252
+ private get batch() {
253
+ if (this._batch != null) return this._batch;
254
+ const cursor = this.cursor;
255
+ if (cursor.has('firstBatch')) this._batch = cursor.get('firstBatch', BSONType.array, true);
256
+ else if (cursor.has('nextBatch')) this._batch = cursor.get('nextBatch', BSONType.array, true);
257
+ else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
258
+ return this._batch;
259
+ }
260
+
261
+ public get batchSize() {
262
+ return this.batch?.size();
209
263
  }
210
264
 
211
- shift(options?: BSONSerializeOptions): any {
265
+ public get postBatchResumeToken() {
266
+ return (
267
+ this.cursor.get('postBatchResumeToken', BSONType.object)?.toObject({
268
+ promoteValues: false,
269
+ promoteLongs: false,
270
+ promoteBuffers: false
271
+ }) ?? null
272
+ );
273
+ }
274
+
275
+ public shift(options?: BSONSerializeOptions): any {
212
276
  if (this.iterated >= this.batchSize) {
213
277
  return null;
214
278
  }
215
279
 
216
280
  const result = this.batch.get(this.iterated, BSONType.object, true) ?? null;
281
+ const encryptedResult = this.encryptedBatch?.get(this.iterated, BSONType.object, true) ?? null;
282
+
217
283
  this.iterated += 1;
218
284
 
219
285
  if (options?.raw) {
220
286
  return result.toBytes();
221
287
  } else {
222
- return result.toObject(options);
288
+ const object = result.toObject(options);
289
+ if (encryptedResult) {
290
+ decorateDecryptionResult(object, encryptedResult.toObject(options), true);
291
+ }
292
+ return object;
223
293
  }
224
294
  }
225
295
 
226
- clear() {
296
+ public clear() {
227
297
  this.iterated = this.batchSize;
228
298
  }
299
+ }
300
+
301
+ /**
302
+ * Explain responses have nothing to do with cursor responses
303
+ * This class serves to temporarily avoid refactoring how cursors handle
304
+ * explain responses which is to detect that the response is not cursor-like and return the explain
305
+ * result as the "first and only" document in the "batch" and end the "cursor"
306
+ */
307
+ export class ExplainedCursorResponse extends CursorResponse {
308
+ isExplain = true;
309
+
310
+ override get id(): Long {
311
+ return Long.fromBigInt(0n);
312
+ }
313
+
314
+ override get batchSize() {
315
+ return 0;
316
+ }
317
+
318
+ override get ns() {
319
+ return null;
320
+ }
229
321
 
230
- pushMany() {
231
- throw new Error('pushMany Unsupported method');
322
+ _length = 1;
323
+ override get length(): number {
324
+ return this._length;
232
325
  }
233
326
 
234
- push() {
235
- throw new Error('push Unsupported method');
327
+ override shift(options?: BSONSerializeOptions | undefined) {
328
+ if (this._length === 0) return null;
329
+ this._length -= 1;
330
+ return this.toObject(options);
236
331
  }
237
332
  }
package/src/constants.ts CHANGED
@@ -165,3 +165,12 @@ export const LEGACY_HELLO_COMMAND = 'ismaster';
165
165
  * The legacy hello command that was deprecated in MongoDB 5.0.
166
166
  */
167
167
  export const LEGACY_HELLO_COMMAND_CAMEL_CASE = 'isMaster';
168
+
169
+ // Typescript errors if we index objects with `Symbol.for(...)`, so
170
+ // to avoid TS errors we pull them out into variables. Then we can type
171
+ // the objects (and class) that we expect to see them on and prevent TS
172
+ // errors.
173
+ /** @internal */
174
+ export const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
175
+ /** @internal */
176
+ export const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
@@ -1,7 +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
+ import { type CursorResponse } from '../cmap/wire_protocol/responses';
5
5
  import {
6
6
  MongoAPIError,
7
7
  MongoCursorExhaustedError,
@@ -11,15 +11,33 @@ import {
11
11
  MongoTailableCursorError
12
12
  } from '../error';
13
13
  import type { MongoClient } from '../mongo_client';
14
- import { type TODO_NODE_3286, TypedEventEmitter } from '../mongo_types';
15
- import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
14
+ import { TypedEventEmitter } from '../mongo_types';
15
+ import { executeOperation } from '../operations/execute_operation';
16
16
  import { GetMoreOperation } from '../operations/get_more';
17
17
  import { KillCursorsOperation } from '../operations/kill_cursors';
18
18
  import { ReadConcern, type ReadConcernLike } from '../read_concern';
19
19
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
20
20
  import type { Server } from '../sdam/server';
21
21
  import { ClientSession, maybeClearPinnedConnection } from '../sessions';
22
- import { List, type MongoDBNamespace, ns, squashError } from '../utils';
22
+ import { type MongoDBNamespace, squashError } from '../utils';
23
+
24
+ /**
25
+ * @internal
26
+ * TODO(NODE-2882): A cursor's getMore commands must be run on the same server it was started on
27
+ * and the same session must be used for the lifetime of the cursor. This object serves to get the
28
+ * server and session (along with the response) out of executeOperation back to the AbstractCursor.
29
+ *
30
+ * There may be a better design for communicating these values back to the cursor, currently an operation
31
+ * MUST store the selected server on itself so it can be read after executeOperation has returned.
32
+ */
33
+ export interface InitialCursorResponse {
34
+ /** The server selected for the operation */
35
+ server: Server;
36
+ /** The session used for this operation, may be implicitly created */
37
+ session?: ClientSession;
38
+ /** The raw server response for the operation */
39
+ response: CursorResponse;
40
+ }
23
41
 
24
42
  /** @public */
25
43
  export const CURSOR_FLAGS = [
@@ -118,13 +136,7 @@ export abstract class AbstractCursor<
118
136
  /** @internal */
119
137
  private cursorNamespace: MongoDBNamespace;
120
138
  /** @internal */
121
- private documents: {
122
- length: number;
123
- shift(bsonOptions?: any): TSchema | null;
124
- clear(): void;
125
- pushMany(many: Iterable<TSchema>): void;
126
- push(item: TSchema): void;
127
- };
139
+ private documents: CursorResponse | null = null;
128
140
  /** @internal */
129
141
  private cursorClient: MongoClient;
130
142
  /** @internal */
@@ -155,7 +167,6 @@ export abstract class AbstractCursor<
155
167
  this.cursorClient = client;
156
168
  this.cursorNamespace = namespace;
157
169
  this.cursorId = null;
158
- this.documents = new List();
159
170
  this.initialized = false;
160
171
  this.isClosed = false;
161
172
  this.isKilled = false;
@@ -252,16 +263,19 @@ export abstract class AbstractCursor<
252
263
 
253
264
  /** Returns current buffered documents length */
254
265
  bufferedCount(): number {
255
- return this.documents.length;
266
+ return this.documents?.length ?? 0;
256
267
  }
257
268
 
258
269
  /** Returns current buffered documents */
259
270
  readBufferedDocuments(number?: number): TSchema[] {
260
271
  const bufferedDocs: TSchema[] = [];
261
- const documentsToRead = Math.min(number ?? this.documents.length, this.documents.length);
272
+ const documentsToRead = Math.min(
273
+ number ?? this.documents?.length ?? 0,
274
+ this.documents?.length ?? 0
275
+ );
262
276
 
263
277
  for (let count = 0; count < documentsToRead; count++) {
264
- const document = this.documents.shift(this.cursorOptions);
278
+ const document = this.documents?.shift(this.cursorOptions);
265
279
  if (document != null) {
266
280
  bufferedDocs.push(document);
267
281
  }
@@ -269,7 +283,6 @@ export abstract class AbstractCursor<
269
283
 
270
284
  return bufferedDocs;
271
285
  }
272
-
273
286
  async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
274
287
  if (this.isClosed) {
275
288
  return;
@@ -281,11 +294,11 @@ export abstract class AbstractCursor<
281
294
  return;
282
295
  }
283
296
 
284
- if (this.isClosed && this.documents.length === 0) {
297
+ if (this.closed && (this.documents?.length ?? 0) === 0) {
285
298
  return;
286
299
  }
287
300
 
288
- if (this.cursorId != null && this.isDead && this.documents.length === 0) {
301
+ if (this.cursorId != null && this.isDead && (this.documents?.length ?? 0) === 0) {
289
302
  return;
290
303
  }
291
304
 
@@ -347,11 +360,11 @@ export abstract class AbstractCursor<
347
360
  }
348
361
 
349
362
  do {
350
- if (this.documents.length !== 0) {
363
+ if ((this.documents?.length ?? 0) !== 0) {
351
364
  return true;
352
365
  }
353
366
  await this.fetchBatch();
354
- } while (!this.isDead || this.documents.length !== 0);
367
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
355
368
 
356
369
  return false;
357
370
  }
@@ -363,13 +376,13 @@ export abstract class AbstractCursor<
363
376
  }
364
377
 
365
378
  do {
366
- const doc = this.documents.shift();
379
+ const doc = this.documents?.shift(this.cursorOptions);
367
380
  if (doc != null) {
368
381
  if (this.transform != null) return await this.transformDocument(doc);
369
382
  return doc;
370
383
  }
371
384
  await this.fetchBatch();
372
- } while (!this.isDead || this.documents.length !== 0);
385
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
373
386
 
374
387
  return null;
375
388
  }
@@ -382,7 +395,7 @@ export abstract class AbstractCursor<
382
395
  throw new MongoCursorExhaustedError();
383
396
  }
384
397
 
385
- let doc = this.documents.shift();
398
+ let doc = this.documents?.shift(this.cursorOptions);
386
399
  if (doc != null) {
387
400
  if (this.transform != null) return await this.transformDocument(doc);
388
401
  return doc;
@@ -390,7 +403,7 @@ export abstract class AbstractCursor<
390
403
 
391
404
  await this.fetchBatch();
392
405
 
393
- doc = this.documents.shift();
406
+ doc = this.documents?.shift(this.cursorOptions);
394
407
  if (doc != null) {
395
408
  if (this.transform != null) return await this.transformDocument(doc);
396
409
  return doc;
@@ -591,7 +604,7 @@ export abstract class AbstractCursor<
591
604
  }
592
605
 
593
606
  this.cursorId = null;
594
- this.documents.clear();
607
+ this.documents?.clear();
595
608
  this.isClosed = false;
596
609
  this.isKilled = false;
597
610
  this.initialized = false;
@@ -615,10 +628,12 @@ export abstract class AbstractCursor<
615
628
  abstract clone(): AbstractCursor<TSchema>;
616
629
 
617
630
  /** @internal */
618
- protected abstract _initialize(session: ClientSession | undefined): Promise<ExecutionResult>;
631
+ protected abstract _initialize(
632
+ session: ClientSession | undefined
633
+ ): Promise<InitialCursorResponse>;
619
634
 
620
635
  /** @internal */
621
- async getMore(batchSize: number, useCursorResponse = false): Promise<Document | null> {
636
+ async getMore(batchSize: number): Promise<CursorResponse> {
622
637
  if (this.cursorId == null) {
623
638
  throw new MongoRuntimeError(
624
639
  'Unexpected null cursor id. A cursor creating command should have set this'
@@ -636,8 +651,7 @@ export abstract class AbstractCursor<
636
651
  {
637
652
  ...this.cursorOptions,
638
653
  session: this.cursorSession,
639
- batchSize,
640
- useCursorResponse
654
+ batchSize
641
655
  }
642
656
  );
643
657
 
@@ -656,27 +670,10 @@ export abstract class AbstractCursor<
656
670
  const state = await this._initialize(this.cursorSession);
657
671
  const response = state.response;
658
672
  this.selectedServer = state.server;
659
- if (CursorResponse.is(response)) {
660
- this.cursorId = response.id;
661
- if (response.ns) this.cursorNamespace = response.ns;
662
- this.documents = response;
663
- } else if (response.cursor) {
664
- // TODO(NODE-2674): Preserve int64 sent from MongoDB
665
- this.cursorId = getCursorId(response);
666
- if (response.cursor.ns) this.cursorNamespace = ns(response.cursor.ns);
667
- this.documents.pushMany(response.cursor.firstBatch);
668
- }
669
-
670
- if (this.cursorId == null) {
671
- // When server responses return without a cursor document, we close this cursor
672
- // and return the raw server response. This is the case for explain commands
673
- this.cursorId = Long.ZERO;
674
- // TODO(NODE-3286): ExecutionResult needs to accept a generic parameter
675
- this.documents.push(state.response as TODO_NODE_3286);
676
- }
677
-
678
- // the cursor is now initialized, even if it is dead
679
- this.initialized = true;
673
+ this.cursorId = response.id;
674
+ this.cursorNamespace = response.ns ?? this.namespace;
675
+ this.documents = response;
676
+ this.initialized = true; // the cursor is now initialized, even if it is dead
680
677
  } catch (error) {
681
678
  // the cursor is now initialized, even if an error occurred
682
679
  this.initialized = true;
@@ -708,7 +705,7 @@ export abstract class AbstractCursor<
708
705
  if (this.cursorId == null) {
709
706
  await this.cursorInit();
710
707
  // If the cursor died or returned documents, return
711
- if (this.documents.length !== 0 || this.isDead) return;
708
+ if ((this.documents?.length ?? 0) !== 0 || this.isDead) return;
712
709
  // Otherwise, run a getMore
713
710
  }
714
711
 
@@ -717,16 +714,8 @@ export abstract class AbstractCursor<
717
714
 
718
715
  try {
719
716
  const response = await this.getMore(batchSize);
720
- // CursorResponse is disabled in this PR
721
- // however the special `emptyGetMore` can be returned from find cursors
722
- if (CursorResponse.is(response)) {
723
- this.cursorId = response.id;
724
- this.documents = response;
725
- } else if (response?.cursor) {
726
- const cursorId = getCursorId(response);
727
- this.documents.pushMany(response.cursor.nextBatch);
728
- this.cursorId = cursorId;
729
- }
717
+ this.cursorId = response.id;
718
+ this.documents = response;
730
719
  } catch (error) {
731
720
  try {
732
721
  await this.cleanup(error);
@@ -789,7 +778,7 @@ export abstract class AbstractCursor<
789
778
  /** @internal */
790
779
  private emitClose() {
791
780
  try {
792
- if (!this.hasEmittedClose && (this.documents.length === 0 || this.isClosed)) {
781
+ if (!this.hasEmittedClose && ((this.documents?.length ?? 0) === 0 || this.isClosed)) {
793
782
  // @ts-expect-error: CursorEvents is generic so Parameters<CursorEvents["close"]> may not be assignable to `[]`. Not sure how to require extenders do not add parameters.
794
783
  this.emit('close');
795
784
  }
@@ -827,15 +816,6 @@ export abstract class AbstractCursor<
827
816
  }
828
817
  }
829
818
 
830
- /** A temporary helper to box up the many possible type issue of cursor ids */
831
- function getCursorId(response: Document) {
832
- return typeof response.cursor.id === 'number'
833
- ? Long.fromNumber(response.cursor.id)
834
- : typeof response.cursor.id === 'bigint'
835
- ? Long.fromBigInt(response.cursor.id)
836
- : response.cursor.id;
837
- }
838
-
839
819
  class ReadableCursorStream extends Readable {
840
820
  private _cursor: AbstractCursor;
841
821
  private _readInProgress = false;
@@ -2,12 +2,12 @@ import type { Document } from '../bson';
2
2
  import type { ExplainVerbosityLike } from '../explain';
3
3
  import type { MongoClient } from '../mongo_client';
4
4
  import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
5
- import { executeOperation, type ExecutionResult } from '../operations/execute_operation';
5
+ import { executeOperation } from '../operations/execute_operation';
6
6
  import type { ClientSession } from '../sessions';
7
7
  import type { Sort } from '../sort';
8
8
  import type { MongoDBNamespace } from '../utils';
9
9
  import { mergeOptions } from '../utils';
10
- import type { AbstractCursorOptions } from './abstract_cursor';
10
+ import type { AbstractCursorOptions, InitialCursorResponse } from './abstract_cursor';
11
11
  import { AbstractCursor } from './abstract_cursor';
12
12
 
13
13
  /** @public */
@@ -51,7 +51,7 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
51
51
  }
52
52
 
53
53
  /** @internal */
54
- async _initialize(session: ClientSession): Promise<ExecutionResult> {
54
+ async _initialize(session: ClientSession): Promise<InitialCursorResponse> {
55
55
  const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
56
56
  ...this.aggregateOptions,
57
57
  ...this.cursorOptions,
@@ -60,20 +60,21 @@ export class AggregationCursor<TSchema = any> extends AbstractCursor<TSchema> {
60
60
 
61
61
  const response = await executeOperation(this.client, aggregateOperation);
62
62
 
63
- // TODO: NODE-2882
64
63
  return { server: aggregateOperation.server, session, response };
65
64
  }
66
65
 
67
66
  /** Execute the explain for the cursor */
68
67
  async explain(verbosity?: ExplainVerbosityLike): Promise<Document> {
69
- return await executeOperation(
70
- this.client,
71
- new AggregateOperation(this.namespace, this.pipeline, {
72
- ...this.aggregateOptions, // NOTE: order matters here, we may need to refine this
73
- ...this.cursorOptions,
74
- explain: verbosity ?? true
75
- })
76
- );
68
+ return (
69
+ await executeOperation(
70
+ this.client,
71
+ new AggregateOperation(this.namespace, this.pipeline, {
72
+ ...this.aggregateOptions, // NOTE: order matters here, we may need to refine this
73
+ ...this.cursorOptions,
74
+ explain: verbosity ?? true
75
+ })
76
+ )
77
+ ).shift(this.aggregateOptions);
77
78
  }
78
79
 
79
80
  /** Add a stage to the aggregation pipeline