mongodb 6.7.0 → 6.8.0-dev.20240629.sha.d85f827a

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 (105) hide show
  1. package/README.md +29 -1
  2. package/lib/bson.js.map +1 -1
  3. package/lib/client-side-encryption/auto_encrypter.js +8 -61
  4. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  5. package/lib/client-side-encryption/client_encryption.js +5 -5
  6. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  7. package/lib/client-side-encryption/providers/index.js.map +1 -1
  8. package/lib/client-side-encryption/state_machine.js +15 -11
  9. package/lib/client-side-encryption/state_machine.js.map +1 -1
  10. package/lib/cmap/connection.js +22 -20
  11. package/lib/cmap/connection.js.map +1 -1
  12. package/lib/cmap/wire_protocol/constants.js +2 -2
  13. package/lib/cmap/wire_protocol/on_demand/document.js +8 -5
  14. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  15. package/lib/cmap/wire_protocol/responses.js +116 -40
  16. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  17. package/lib/collection.js +13 -2
  18. package/lib/collection.js.map +1 -1
  19. package/lib/constants.js +9 -1
  20. package/lib/constants.js.map +1 -1
  21. package/lib/cursor/abstract_cursor.js +231 -285
  22. package/lib/cursor/abstract_cursor.js.map +1 -1
  23. package/lib/cursor/aggregation_cursor.js +11 -19
  24. package/lib/cursor/aggregation_cursor.js.map +1 -1
  25. package/lib/cursor/change_stream_cursor.js +12 -14
  26. package/lib/cursor/change_stream_cursor.js.map +1 -1
  27. package/lib/cursor/find_cursor.js +64 -84
  28. package/lib/cursor/find_cursor.js.map +1 -1
  29. package/lib/cursor/list_collections_cursor.js +0 -1
  30. package/lib/cursor/list_collections_cursor.js.map +1 -1
  31. package/lib/cursor/list_indexes_cursor.js +0 -1
  32. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  33. package/lib/cursor/run_command_cursor.js +4 -6
  34. package/lib/cursor/run_command_cursor.js.map +1 -1
  35. package/lib/error.js +10 -23
  36. package/lib/error.js.map +1 -1
  37. package/lib/index.js.map +1 -1
  38. package/lib/operations/aggregate.js +2 -2
  39. package/lib/operations/aggregate.js.map +1 -1
  40. package/lib/operations/bulk_write.js +1 -2
  41. package/lib/operations/bulk_write.js.map +1 -1
  42. package/lib/operations/command.js +2 -3
  43. package/lib/operations/command.js.map +1 -1
  44. package/lib/operations/execute_operation.js.map +1 -1
  45. package/lib/operations/find.js +2 -1
  46. package/lib/operations/find.js.map +1 -1
  47. package/lib/operations/get_more.js +1 -1
  48. package/lib/operations/get_more.js.map +1 -1
  49. package/lib/operations/indexes.js +2 -1
  50. package/lib/operations/indexes.js.map +1 -1
  51. package/lib/operations/list_collections.js +2 -1
  52. package/lib/operations/list_collections.js.map +1 -1
  53. package/lib/operations/run_command.js +1 -1
  54. package/lib/operations/run_command.js.map +1 -1
  55. package/lib/operations/update.js +2 -1
  56. package/lib/operations/update.js.map +1 -1
  57. package/lib/sdam/server.js +7 -2
  58. package/lib/sdam/server.js.map +1 -1
  59. package/lib/sessions.js +1 -1
  60. package/lib/sessions.js.map +1 -1
  61. package/lib/utils.js +45 -1
  62. package/lib/utils.js.map +1 -1
  63. package/lib/write_concern.js +17 -1
  64. package/lib/write_concern.js.map +1 -1
  65. package/mongodb.d.ts +188 -221
  66. package/package.json +3 -3
  67. package/src/bson.ts +1 -0
  68. package/src/client-side-encryption/auto_encrypter.ts +10 -149
  69. package/src/client-side-encryption/client_encryption.ts +33 -19
  70. package/src/client-side-encryption/providers/index.ts +118 -92
  71. package/src/client-side-encryption/state_machine.ts +22 -18
  72. package/src/cmap/connection.ts +46 -50
  73. package/src/cmap/wire_protocol/constants.ts +2 -2
  74. package/src/cmap/wire_protocol/on_demand/document.ts +13 -6
  75. package/src/cmap/wire_protocol/responses.ts +140 -45
  76. package/src/collection.ts +25 -5
  77. package/src/constants.ts +9 -0
  78. package/src/cursor/abstract_cursor.ts +280 -373
  79. package/src/cursor/aggregation_cursor.ts +24 -33
  80. package/src/cursor/change_stream_cursor.ts +31 -48
  81. package/src/cursor/find_cursor.ts +77 -92
  82. package/src/cursor/list_collections_cursor.ts +3 -4
  83. package/src/cursor/list_indexes_cursor.ts +3 -4
  84. package/src/cursor/run_command_cursor.ts +13 -19
  85. package/src/error.ts +20 -30
  86. package/src/index.ts +19 -10
  87. package/src/operations/aggregate.ts +12 -5
  88. package/src/operations/bulk_write.ts +1 -2
  89. package/src/operations/command.ts +17 -3
  90. package/src/operations/delete.ts +2 -2
  91. package/src/operations/execute_operation.ts +0 -13
  92. package/src/operations/find.ts +7 -3
  93. package/src/operations/find_and_modify.ts +1 -1
  94. package/src/operations/get_more.ts +6 -10
  95. package/src/operations/indexes.ts +7 -3
  96. package/src/operations/list_collections.ts +8 -3
  97. package/src/operations/run_command.ts +16 -6
  98. package/src/operations/update.ts +2 -1
  99. package/src/sdam/server.ts +7 -2
  100. package/src/sessions.ts +1 -1
  101. package/src/utils.ts +52 -2
  102. package/src/write_concern.ts +18 -0
  103. package/lib/operations/count_documents.js +0 -31
  104. package/lib/operations/count_documents.js.map +0 -1
  105. package/src/operations/count_documents.ts +0 -46
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertUninitialized = exports.AbstractCursor = exports.CURSOR_FLAGS = void 0;
3
+ exports.AbstractCursor = exports.CURSOR_FLAGS = void 0;
4
4
  const stream_1 = require("stream");
5
5
  const bson_1 = require("../bson");
6
- const responses_1 = require("../cmap/wire_protocol/responses");
7
6
  const error_1 = require("../error");
8
7
  const mongo_types_1 = require("../mongo_types");
9
8
  const execute_operation_1 = require("../operations/execute_operation");
@@ -13,30 +12,6 @@ const read_concern_1 = require("../read_concern");
13
12
  const read_preference_1 = require("../read_preference");
14
13
  const sessions_1 = require("../sessions");
15
14
  const utils_1 = require("../utils");
16
- /** @internal */
17
- const kId = Symbol('id');
18
- /** @internal */
19
- const kDocuments = Symbol('documents');
20
- /** @internal */
21
- const kServer = Symbol('server');
22
- /** @internal */
23
- const kNamespace = Symbol('namespace');
24
- /** @internal */
25
- const kClient = Symbol('client');
26
- /** @internal */
27
- const kSession = Symbol('session');
28
- /** @internal */
29
- const kOptions = Symbol('options');
30
- /** @internal */
31
- const kTransform = Symbol('transform');
32
- /** @internal */
33
- const kInitialized = Symbol('initialized');
34
- /** @internal */
35
- const kClosed = Symbol('closed');
36
- /** @internal */
37
- const kKilled = Symbol('killed');
38
- /** @internal */
39
- const kInit = Symbol('kInit');
40
15
  /** @public */
41
16
  exports.CURSOR_FLAGS = [
42
17
  'tailable',
@@ -51,102 +26,115 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
51
26
  /** @internal */
52
27
  constructor(client, namespace, options = {}) {
53
28
  super();
29
+ /** @internal */
30
+ this.documents = null;
31
+ /** @internal */
32
+ this.hasEmittedClose = false;
54
33
  if (!client.s.isMongoClient) {
55
34
  throw new error_1.MongoRuntimeError('Cursor must be constructed with MongoClient');
56
35
  }
57
- this[kClient] = client;
58
- this[kNamespace] = namespace;
59
- this[kId] = null;
60
- this[kDocuments] = new utils_1.List();
61
- this[kInitialized] = false;
62
- this[kClosed] = false;
63
- this[kKilled] = false;
64
- this[kOptions] = {
36
+ this.cursorClient = client;
37
+ this.cursorNamespace = namespace;
38
+ this.cursorId = null;
39
+ this.initialized = false;
40
+ this.isClosed = false;
41
+ this.isKilled = false;
42
+ this.cursorOptions = {
65
43
  readPreference: options.readPreference && options.readPreference instanceof read_preference_1.ReadPreference
66
44
  ? options.readPreference
67
45
  : read_preference_1.ReadPreference.primary,
68
46
  ...(0, bson_1.pluckBSONSerializeOptions)(options)
69
47
  };
70
- this[kOptions].timeoutMS = options.timeoutMS;
48
+ this.cursorOptions.timeoutMS = options.timeoutMS;
71
49
  const readConcern = read_concern_1.ReadConcern.fromOptions(options);
72
50
  if (readConcern) {
73
- this[kOptions].readConcern = readConcern;
51
+ this.cursorOptions.readConcern = readConcern;
74
52
  }
75
53
  if (typeof options.batchSize === 'number') {
76
- this[kOptions].batchSize = options.batchSize;
54
+ this.cursorOptions.batchSize = options.batchSize;
77
55
  }
78
56
  // we check for undefined specifically here to allow falsy values
79
57
  // eslint-disable-next-line no-restricted-syntax
80
58
  if (options.comment !== undefined) {
81
- this[kOptions].comment = options.comment;
59
+ this.cursorOptions.comment = options.comment;
82
60
  }
83
61
  if (typeof options.maxTimeMS === 'number') {
84
- this[kOptions].maxTimeMS = options.maxTimeMS;
62
+ this.cursorOptions.maxTimeMS = options.maxTimeMS;
85
63
  }
86
64
  if (typeof options.maxAwaitTimeMS === 'number') {
87
- this[kOptions].maxAwaitTimeMS = options.maxAwaitTimeMS;
65
+ this.cursorOptions.maxAwaitTimeMS = options.maxAwaitTimeMS;
88
66
  }
89
67
  if (options.session instanceof sessions_1.ClientSession) {
90
- this[kSession] = options.session;
68
+ this.cursorSession = options.session;
91
69
  }
92
70
  else {
93
- this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
71
+ this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
94
72
  }
95
73
  }
74
+ /**
75
+ * The cursor has no id until it receives a response from the initial cursor creating command.
76
+ *
77
+ * It is non-zero for as long as the database has an open cursor.
78
+ *
79
+ * The initiating command may receive a zero id if the entire result is in the `firstBatch`.
80
+ */
96
81
  get id() {
97
- return this[kId] ?? undefined;
82
+ return this.cursorId ?? undefined;
98
83
  }
99
84
  /** @internal */
100
85
  get isDead() {
101
- return (this[kId]?.isZero() ?? false) || this[kClosed] || this[kKilled];
86
+ return (this.cursorId?.isZero() ?? false) || this.isClosed || this.isKilled;
102
87
  }
103
88
  /** @internal */
104
89
  get client() {
105
- return this[kClient];
90
+ return this.cursorClient;
106
91
  }
107
92
  /** @internal */
108
93
  get server() {
109
- return this[kServer];
94
+ return this.selectedServer;
110
95
  }
111
96
  get namespace() {
112
- return this[kNamespace];
97
+ return this.cursorNamespace;
113
98
  }
114
99
  get readPreference() {
115
- return this[kOptions].readPreference;
100
+ return this.cursorOptions.readPreference;
116
101
  }
117
102
  get readConcern() {
118
- return this[kOptions].readConcern;
103
+ return this.cursorOptions.readConcern;
119
104
  }
120
105
  /** @internal */
121
106
  get session() {
122
- return this[kSession];
107
+ return this.cursorSession;
123
108
  }
124
109
  set session(clientSession) {
125
- this[kSession] = clientSession;
126
- }
127
- /** @internal */
128
- get cursorOptions() {
129
- return this[kOptions];
110
+ this.cursorSession = clientSession;
130
111
  }
112
+ /**
113
+ * The cursor is closed and all remaining locally buffered documents have been iterated.
114
+ */
131
115
  get closed() {
132
- return this[kClosed];
116
+ return this.isClosed && (this.documents?.length ?? 0) === 0;
133
117
  }
118
+ /**
119
+ * A `killCursors` command was attempted on this cursor.
120
+ * This is performed if the cursor id is non zero.
121
+ */
134
122
  get killed() {
135
- return this[kKilled];
123
+ return this.isKilled;
136
124
  }
137
125
  get loadBalanced() {
138
- return !!this[kClient].topology?.loadBalanced;
126
+ return !!this.cursorClient.topology?.loadBalanced;
139
127
  }
140
128
  /** Returns current buffered documents length */
141
129
  bufferedCount() {
142
- return this[kDocuments].length;
130
+ return this.documents?.length ?? 0;
143
131
  }
144
132
  /** Returns current buffered documents */
145
133
  readBufferedDocuments(number) {
146
134
  const bufferedDocs = [];
147
- const documentsToRead = Math.min(number ?? this[kDocuments].length, this[kDocuments].length);
135
+ const documentsToRead = Math.min(number ?? this.documents?.length ?? 0, this.documents?.length ?? 0);
148
136
  for (let count = 0; count < documentsToRead; count++) {
149
- const document = this[kDocuments].shift(this[kOptions]);
137
+ const document = this.documents?.shift(this.cursorOptions);
150
138
  if (document != null) {
151
139
  bufferedDocs.push(document);
152
140
  }
@@ -154,39 +142,32 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
154
142
  return bufferedDocs;
155
143
  }
156
144
  async *[Symbol.asyncIterator]() {
157
- if (this.closed) {
145
+ if (this.isClosed) {
158
146
  return;
159
147
  }
160
148
  try {
161
149
  while (true) {
150
+ if (this.isKilled) {
151
+ return;
152
+ }
153
+ if (this.closed) {
154
+ return;
155
+ }
156
+ if (this.cursorId != null && this.isDead && (this.documents?.length ?? 0) === 0) {
157
+ return;
158
+ }
162
159
  const document = await this.next();
163
- // Intentional strict null check, because users can map cursors to falsey values.
164
- // We allow mapping to all values except for null.
165
160
  // eslint-disable-next-line no-restricted-syntax
166
161
  if (document === null) {
167
- if (!this.closed) {
168
- const message = 'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
169
- try {
170
- await cleanupCursor(this, { needsToEmitClosed: true });
171
- }
172
- catch (error) {
173
- (0, utils_1.squashError)(error);
174
- }
175
- throw new error_1.MongoAPIError(message);
176
- }
177
- break;
162
+ return;
178
163
  }
179
164
  yield document;
180
- if (this[kId] === bson_1.Long.ZERO) {
181
- // Cursor exhausted
182
- break;
183
- }
184
165
  }
185
166
  }
186
167
  finally {
187
168
  // Only close the cursor if it has not already been closed. This finally clause handles
188
169
  // the case when a user would break out of a for await of loop early.
189
- if (!this.closed) {
170
+ if (!this.isClosed) {
190
171
  try {
191
172
  await this.close();
192
173
  }
@@ -221,29 +202,54 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
221
202
  return new ReadableCursorStream(this);
222
203
  }
223
204
  async hasNext() {
224
- if (this[kId] === bson_1.Long.ZERO) {
205
+ if (this.cursorId === bson_1.Long.ZERO) {
225
206
  return false;
226
207
  }
227
- if (this[kDocuments].length !== 0) {
228
- return true;
229
- }
230
- return await next(this, { blocking: true, transform: false, shift: false });
208
+ do {
209
+ if ((this.documents?.length ?? 0) !== 0) {
210
+ return true;
211
+ }
212
+ await this.fetchBatch();
213
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
214
+ return false;
231
215
  }
232
216
  /** Get the next available document from the cursor, returns null if no more documents are available. */
233
217
  async next() {
234
- if (this[kId] === bson_1.Long.ZERO) {
218
+ if (this.cursorId === bson_1.Long.ZERO) {
235
219
  throw new error_1.MongoCursorExhaustedError();
236
220
  }
237
- return await next(this, { blocking: true, transform: true, shift: true });
221
+ do {
222
+ const doc = this.documents?.shift(this.cursorOptions);
223
+ if (doc != null) {
224
+ if (this.transform != null)
225
+ return await this.transformDocument(doc);
226
+ return doc;
227
+ }
228
+ await this.fetchBatch();
229
+ } while (!this.isDead || (this.documents?.length ?? 0) !== 0);
230
+ return null;
238
231
  }
239
232
  /**
240
233
  * Try to get the next available document from the cursor or `null` if an empty batch is returned
241
234
  */
242
235
  async tryNext() {
243
- if (this[kId] === bson_1.Long.ZERO) {
236
+ if (this.cursorId === bson_1.Long.ZERO) {
244
237
  throw new error_1.MongoCursorExhaustedError();
245
238
  }
246
- return await next(this, { blocking: false, transform: true, shift: true });
239
+ let doc = this.documents?.shift(this.cursorOptions);
240
+ if (doc != null) {
241
+ if (this.transform != null)
242
+ return await this.transformDocument(doc);
243
+ return doc;
244
+ }
245
+ await this.fetchBatch();
246
+ doc = this.documents?.shift(this.cursorOptions);
247
+ if (doc != null) {
248
+ if (this.transform != null)
249
+ return await this.transformDocument(doc);
250
+ return doc;
251
+ }
252
+ return null;
247
253
  }
248
254
  /**
249
255
  * Iterates over all the documents for this cursor using the iterator, callback pattern.
@@ -265,9 +271,7 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
265
271
  }
266
272
  }
267
273
  async close() {
268
- const needsToEmitClosed = !this[kClosed];
269
- this[kClosed] = true;
270
- await cleanupCursor(this, { needsToEmitClosed });
274
+ await this.cleanup();
271
275
  }
272
276
  /**
273
277
  * Returns an array of documents. The caller is responsible for making sure that there
@@ -289,14 +293,14 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
289
293
  * @param value - The flag boolean value.
290
294
  */
291
295
  addCursorFlag(flag, value) {
292
- assertUninitialized(this);
296
+ this.throwIfInitialized();
293
297
  if (!exports.CURSOR_FLAGS.includes(flag)) {
294
298
  throw new error_1.MongoInvalidArgumentError(`Flag ${flag} is not one of ${exports.CURSOR_FLAGS}`);
295
299
  }
296
300
  if (typeof value !== 'boolean') {
297
301
  throw new error_1.MongoInvalidArgumentError(`Flag ${flag} must be a boolean value`);
298
302
  }
299
- this[kOptions][flag] = value;
303
+ this.cursorOptions[flag] = value;
300
304
  return this;
301
305
  }
302
306
  /**
@@ -342,15 +346,15 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
342
346
  * @param transform - The mapping transformation method.
343
347
  */
344
348
  map(transform) {
345
- assertUninitialized(this);
346
- const oldTransform = this[kTransform]; // TODO(NODE-3283): Improve transform typing
349
+ this.throwIfInitialized();
350
+ const oldTransform = this.transform;
347
351
  if (oldTransform) {
348
- this[kTransform] = doc => {
352
+ this.transform = doc => {
349
353
  return transform(oldTransform(doc));
350
354
  };
351
355
  }
352
356
  else {
353
- this[kTransform] = transform;
357
+ this.transform = transform;
354
358
  }
355
359
  return this;
356
360
  }
@@ -360,12 +364,12 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
360
364
  * @param readPreference - The new read preference for the cursor.
361
365
  */
362
366
  withReadPreference(readPreference) {
363
- assertUninitialized(this);
367
+ this.throwIfInitialized();
364
368
  if (readPreference instanceof read_preference_1.ReadPreference) {
365
- this[kOptions].readPreference = readPreference;
369
+ this.cursorOptions.readPreference = readPreference;
366
370
  }
367
371
  else if (typeof readPreference === 'string') {
368
- this[kOptions].readPreference = read_preference_1.ReadPreference.fromString(readPreference);
372
+ this.cursorOptions.readPreference = read_preference_1.ReadPreference.fromString(readPreference);
369
373
  }
370
374
  else {
371
375
  throw new error_1.MongoInvalidArgumentError(`Invalid read preference: ${readPreference}`);
@@ -378,10 +382,10 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
378
382
  * @param readPreference - The new read preference for the cursor.
379
383
  */
380
384
  withReadConcern(readConcern) {
381
- assertUninitialized(this);
385
+ this.throwIfInitialized();
382
386
  const resolvedReadConcern = read_concern_1.ReadConcern.fromOptions({ readConcern });
383
387
  if (resolvedReadConcern) {
384
- this[kOptions].readConcern = resolvedReadConcern;
388
+ this.cursorOptions.readConcern = resolvedReadConcern;
385
389
  }
386
390
  return this;
387
391
  }
@@ -391,11 +395,11 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
391
395
  * @param value - Number of milliseconds to wait before aborting the query.
392
396
  */
393
397
  maxTimeMS(value) {
394
- assertUninitialized(this);
398
+ this.throwIfInitialized();
395
399
  if (typeof value !== 'number') {
396
400
  throw new error_1.MongoInvalidArgumentError('Argument for maxTimeMS must be a number');
397
401
  }
398
- this[kOptions].maxTimeMS = value;
402
+ this.cursorOptions.maxTimeMS = value;
399
403
  return this;
400
404
  }
401
405
  /**
@@ -404,14 +408,14 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
404
408
  * @param value - The number of documents to return per batch. See {@link https://www.mongodb.com/docs/manual/reference/command/find/|find command documentation}.
405
409
  */
406
410
  batchSize(value) {
407
- assertUninitialized(this);
408
- if (this[kOptions].tailable) {
411
+ this.throwIfInitialized();
412
+ if (this.cursorOptions.tailable) {
409
413
  throw new error_1.MongoTailableCursorError('Tailable cursor does not support batchSize');
410
414
  }
411
415
  if (typeof value !== 'number') {
412
416
  throw new error_1.MongoInvalidArgumentError('Operation "batchSize" requires an integer');
413
417
  }
414
- this[kOptions].batchSize = value;
418
+ this.cursorOptions.batchSize = value;
415
419
  return this;
416
420
  }
417
421
  /**
@@ -420,15 +424,15 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
420
424
  * if the resultant data has already been retrieved by this cursor.
421
425
  */
422
426
  rewind() {
423
- if (!this[kInitialized]) {
427
+ if (!this.initialized) {
424
428
  return;
425
429
  }
426
- this[kId] = null;
427
- this[kDocuments].clear();
428
- this[kClosed] = false;
429
- this[kKilled] = false;
430
- this[kInitialized] = false;
431
- const session = this[kSession];
430
+ this.cursorId = null;
431
+ this.documents?.clear();
432
+ this.isClosed = false;
433
+ this.isKilled = false;
434
+ this.initialized = false;
435
+ const session = this.cursorSession;
432
436
  if (session) {
433
437
  // We only want to end this session if we created it, and it hasn't ended yet
434
438
  if (session.explicit === false) {
@@ -436,20 +440,24 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
436
440
  // eslint-disable-next-line github/no-then
437
441
  session.endSession().then(undefined, utils_1.squashError);
438
442
  }
439
- this[kSession] = this.client.startSession({ owner: this, explicit: false });
443
+ this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
440
444
  }
441
445
  }
442
446
  }
443
447
  /** @internal */
444
- async getMore(batchSize, useCursorResponse = false) {
445
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
446
- const getMoreOperation = new get_more_1.GetMoreOperation(this[kNamespace], this[kId], this[kServer], {
447
- ...this[kOptions],
448
- session: this[kSession],
449
- batchSize,
450
- useCursorResponse
448
+ async getMore(batchSize) {
449
+ if (this.cursorId == null) {
450
+ throw new error_1.MongoRuntimeError('Unexpected null cursor id. A cursor creating command should have set this');
451
+ }
452
+ if (this.selectedServer == null) {
453
+ throw new error_1.MongoRuntimeError('Unexpected null selectedServer. A cursor creating command should have set this');
454
+ }
455
+ const getMoreOperation = new get_more_1.GetMoreOperation(this.cursorNamespace, this.cursorId, this.selectedServer, {
456
+ ...this.cursorOptions,
457
+ session: this.cursorSession,
458
+ batchSize
451
459
  });
452
- return await (0, execute_operation_1.executeOperation)(this[kClient], getMoreOperation);
460
+ return await (0, execute_operation_1.executeOperation)(this.cursorClient, getMoreOperation);
453
461
  }
454
462
  /**
455
463
  * @internal
@@ -458,118 +466,56 @@ class AbstractCursor extends mongo_types_1.TypedEventEmitter {
458
466
  * operation. We cannot refactor to use the abstract _initialize method without
459
467
  * a significant refactor.
460
468
  */
461
- async [kInit]() {
469
+ async cursorInit() {
462
470
  try {
463
- const state = await this._initialize(this[kSession]);
471
+ const state = await this._initialize(this.cursorSession);
464
472
  const response = state.response;
465
- this[kServer] = state.server;
466
- if (responses_1.CursorResponse.is(response)) {
467
- this[kId] = response.id;
468
- if (response.ns)
469
- this[kNamespace] = response.ns;
470
- this[kDocuments] = response;
471
- }
472
- else if (response.cursor) {
473
- // TODO(NODE-2674): Preserve int64 sent from MongoDB
474
- this[kId] =
475
- typeof response.cursor.id === 'number'
476
- ? bson_1.Long.fromNumber(response.cursor.id)
477
- : typeof response.cursor.id === 'bigint'
478
- ? bson_1.Long.fromBigInt(response.cursor.id)
479
- : response.cursor.id;
480
- if (response.cursor.ns) {
481
- this[kNamespace] = (0, utils_1.ns)(response.cursor.ns);
482
- }
483
- this[kDocuments].pushMany(response.cursor.firstBatch);
484
- }
485
- // When server responses return without a cursor document, we close this cursor
486
- // and return the raw server response. This is often the case for explain commands
487
- // for example
488
- if (this[kId] == null) {
489
- this[kId] = bson_1.Long.ZERO;
490
- // TODO(NODE-3286): ExecutionResult needs to accept a generic parameter
491
- this[kDocuments].push(state.response);
492
- }
493
- // the cursor is now initialized, even if it is dead
494
- this[kInitialized] = true;
473
+ this.selectedServer = state.server;
474
+ this.cursorId = response.id;
475
+ this.cursorNamespace = response.ns ?? this.namespace;
476
+ this.documents = response;
477
+ this.initialized = true; // the cursor is now initialized, even if it is dead
495
478
  }
496
479
  catch (error) {
497
480
  // the cursor is now initialized, even if an error occurred
498
- this[kInitialized] = true;
499
- await cleanupCursor(this, { error });
481
+ this.initialized = true;
482
+ await this.cleanup(error);
500
483
  throw error;
501
484
  }
502
485
  if (this.isDead) {
503
- await cleanupCursor(this, undefined);
486
+ await this.cleanup();
504
487
  }
505
488
  return;
506
489
  }
507
- }
508
- /** @event */
509
- AbstractCursor.CLOSE = 'close';
510
- exports.AbstractCursor = AbstractCursor;
511
- async function next(cursor, { blocking, transform, shift }) {
512
- if (cursor.closed) {
513
- if (!shift)
514
- return false;
515
- return null;
516
- }
517
- do {
518
- if (cursor[kId] == null) {
519
- // All cursors must operate within a session, one must be made implicitly if not explicitly provided
520
- await cursor[kInit]();
521
- }
522
- if (cursor[kDocuments].length !== 0) {
523
- if (!shift)
524
- return true;
525
- const doc = cursor[kDocuments].shift(cursor[kOptions]);
526
- if (doc != null && transform && cursor[kTransform]) {
527
- try {
528
- return cursor[kTransform](doc);
529
- }
530
- catch (error) {
531
- try {
532
- await cleanupCursor(cursor, { error, needsToEmitClosed: true });
533
- }
534
- catch (error) {
535
- // `cleanupCursor` should never throw, squash and throw the original error
536
- (0, utils_1.squashError)(error);
537
- }
538
- throw error;
539
- }
540
- }
541
- return doc;
490
+ /** @internal Attempt to obtain more documents */
491
+ async fetchBatch() {
492
+ if (this.isClosed) {
493
+ return;
542
494
  }
543
- if (cursor.isDead) {
495
+ if (this.isDead) {
544
496
  // if the cursor is dead, we clean it up
545
497
  // cleanupCursor should never throw, but if it does it indicates a bug in the driver
546
498
  // and we should surface the error
547
- await cleanupCursor(cursor, {});
548
- if (!shift)
549
- return false;
550
- return null;
499
+ await this.cleanup();
500
+ return;
501
+ }
502
+ if (this.cursorId == null) {
503
+ await this.cursorInit();
504
+ // If the cursor died or returned documents, return
505
+ if ((this.documents?.length ?? 0) !== 0 || this.isDead)
506
+ return;
507
+ // Otherwise, run a getMore
551
508
  }
552
509
  // otherwise need to call getMore
553
- const batchSize = cursor[kOptions].batchSize || 1000;
510
+ const batchSize = this.cursorOptions.batchSize || 1000;
554
511
  try {
555
- const response = await cursor.getMore(batchSize);
556
- if (responses_1.CursorResponse.is(response)) {
557
- cursor[kId] = response.id;
558
- cursor[kDocuments] = response;
559
- }
560
- else if (response) {
561
- const cursorId = typeof response.cursor.id === 'number'
562
- ? bson_1.Long.fromNumber(response.cursor.id)
563
- : typeof response.cursor.id === 'bigint'
564
- ? bson_1.Long.fromBigInt(response.cursor.id)
565
- : response.cursor.id;
566
- cursor[kDocuments].pushMany(response.cursor.nextBatch);
567
- cursor[kId] = cursorId;
568
- }
512
+ const response = await this.getMore(batchSize);
513
+ this.cursorId = response.id;
514
+ this.documents = response;
569
515
  }
570
516
  catch (error) {
571
517
  try {
572
- await cleanupCursor(cursor, { error, needsToEmitClosed: true });
518
+ await this.cleanup(error);
573
519
  }
574
520
  catch (error) {
575
521
  // `cleanupCursor` should never throw, squash and throw the original error
@@ -577,7 +523,7 @@ async function next(cursor, { blocking, transform, shift }) {
577
523
  }
578
524
  throw error;
579
525
  }
580
- if (cursor.isDead) {
526
+ if (this.isDead) {
581
527
  // If we successfully received a response from a cursor BUT the cursor indicates that it is exhausted,
582
528
  // we intentionally clean up the cursor to release its session back into the pool before the cursor
583
529
  // is iterated. This prevents a cursor that is exhausted on the server from holding
@@ -585,89 +531,85 @@ async function next(cursor, { blocking, transform, shift }) {
585
531
  //
586
532
  // cleanupCursorAsync should never throw, but if it does it indicates a bug in the driver
587
533
  // and we should surface the error
588
- await cleanupCursor(cursor, {});
534
+ await this.cleanup();
589
535
  }
590
- if (cursor[kDocuments].length === 0 && blocking === false) {
591
- if (!shift)
592
- return false;
593
- return null;
536
+ }
537
+ /** @internal */
538
+ async cleanup(error) {
539
+ this.isClosed = true;
540
+ const session = this.cursorSession;
541
+ try {
542
+ if (!this.isKilled &&
543
+ this.cursorId &&
544
+ !this.cursorId.isZero() &&
545
+ this.cursorNamespace &&
546
+ this.selectedServer &&
547
+ !session.hasEnded) {
548
+ this.isKilled = true;
549
+ const cursorId = this.cursorId;
550
+ this.cursorId = bson_1.Long.ZERO;
551
+ await (0, execute_operation_1.executeOperation)(this.cursorClient, new kill_cursors_1.KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
552
+ session
553
+ }));
554
+ }
594
555
  }
595
- } while (!cursor.isDead || cursor[kDocuments].length !== 0);
596
- if (!shift)
597
- return false;
598
- return null;
599
- }
600
- async function cleanupCursor(cursor, options) {
601
- const cursorId = cursor[kId];
602
- const cursorNs = cursor[kNamespace];
603
- const server = cursor[kServer];
604
- const session = cursor[kSession];
605
- const error = options?.error;
606
- // Cursors only emit closed events once the client-side cursor has been exhausted fully or there
607
- // was an error. Notably, when the server returns a cursor id of 0 and a non-empty batch, we
608
- // cleanup the cursor but don't emit a `close` event.
609
- const needsToEmitClosed = options?.needsToEmitClosed ?? cursor[kDocuments].length === 0;
610
- if (error) {
611
- if (cursor.loadBalanced && error instanceof error_1.MongoNetworkError) {
612
- return await completeCleanup();
613
- }
614
- }
615
- if (cursorId == null || server == null || cursorId.isZero() || cursorNs == null) {
616
- if (needsToEmitClosed) {
617
- cursor[kClosed] = true;
618
- cursor[kId] = bson_1.Long.ZERO;
619
- cursor.emit(AbstractCursor.CLOSE);
556
+ catch (error) {
557
+ (0, utils_1.squashError)(error);
620
558
  }
621
- if (session) {
622
- if (session.owner === cursor) {
559
+ finally {
560
+ if (session?.owner === this) {
623
561
  await session.endSession({ error });
624
- return;
625
562
  }
626
- if (!session.inTransaction()) {
563
+ if (!session?.inTransaction()) {
627
564
  (0, sessions_1.maybeClearPinnedConnection)(session, { error });
628
565
  }
566
+ this.emitClose();
629
567
  }
630
- return;
631
568
  }
632
- async function completeCleanup() {
633
- if (session) {
634
- if (session.owner === cursor) {
635
- try {
636
- await session.endSession({ error });
637
- }
638
- finally {
639
- cursor.emit(AbstractCursor.CLOSE);
640
- }
641
- return;
642
- }
643
- if (!session.inTransaction()) {
644
- (0, sessions_1.maybeClearPinnedConnection)(session, { error });
569
+ /** @internal */
570
+ emitClose() {
571
+ try {
572
+ if (!this.hasEmittedClose && ((this.documents?.length ?? 0) === 0 || this.isClosed)) {
573
+ // @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.
574
+ this.emit('close');
645
575
  }
646
576
  }
647
- cursor.emit(AbstractCursor.CLOSE);
648
- return;
649
- }
650
- cursor[kKilled] = true;
651
- if (session.hasEnded) {
652
- return await completeCleanup();
653
- }
654
- try {
655
- await (0, execute_operation_1.executeOperation)(cursor[kClient], new kill_cursors_1.KillCursorsOperation(cursorId, cursorNs, server, { session }));
656
- }
657
- catch (error) {
658
- (0, utils_1.squashError)(error);
577
+ finally {
578
+ this.hasEmittedClose = true;
579
+ }
659
580
  }
660
- finally {
661
- await completeCleanup();
581
+ /** @internal */
582
+ async transformDocument(document) {
583
+ if (this.transform == null)
584
+ return document;
585
+ try {
586
+ const transformedDocument = this.transform(document);
587
+ // eslint-disable-next-line no-restricted-syntax
588
+ if (transformedDocument === null) {
589
+ const TRANSFORM_TO_NULL_ERROR = 'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
590
+ throw new error_1.MongoAPIError(TRANSFORM_TO_NULL_ERROR);
591
+ }
592
+ return transformedDocument;
593
+ }
594
+ catch (transformError) {
595
+ try {
596
+ await this.close();
597
+ }
598
+ catch (closeError) {
599
+ (0, utils_1.squashError)(closeError);
600
+ }
601
+ throw transformError;
602
+ }
662
603
  }
663
- }
664
- /** @internal */
665
- function assertUninitialized(cursor) {
666
- if (cursor[kInitialized]) {
667
- throw new error_1.MongoCursorInUseError();
604
+ /** @internal */
605
+ throwIfInitialized() {
606
+ if (this.initialized)
607
+ throw new error_1.MongoCursorInUseError();
668
608
  }
669
609
  }
670
- exports.assertUninitialized = assertUninitialized;
610
+ /** @event */
611
+ AbstractCursor.CLOSE = 'close';
612
+ exports.AbstractCursor = AbstractCursor;
671
613
  class ReadableCursorStream extends stream_1.Readable {
672
614
  constructor(cursor) {
673
615
  super({
@@ -690,8 +632,12 @@ class ReadableCursorStream extends stream_1.Readable {
690
632
  this._cursor.close().then(() => callback(error), closeError => callback(closeError));
691
633
  }
692
634
  _readNext() {
635
+ if (this._cursor.id === bson_1.Long.ZERO) {
636
+ this.push(null);
637
+ return;
638
+ }
693
639
  // eslint-disable-next-line github/no-then
694
- next(this._cursor, { blocking: true, transform: true, shift: true }).then(result => {
640
+ this._cursor.next().then(result => {
695
641
  if (result == null) {
696
642
  this.push(null);
697
643
  }