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