mongodb 6.7.0 → 6.8.0
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 +20 -1
- package/lib/bson.js.map +1 -1
- package/lib/client-side-encryption/auto_encrypter.js +8 -61
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +5 -5
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/client-side-encryption/providers/index.js.map +1 -1
- package/lib/client-side-encryption/state_machine.js +15 -11
- package/lib/client-side-encryption/state_machine.js.map +1 -1
- package/lib/cmap/connection.js +22 -20
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js +8 -5
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
- package/lib/cmap/wire_protocol/responses.js +116 -40
- package/lib/cmap/wire_protocol/responses.js.map +1 -1
- package/lib/collection.js +13 -2
- package/lib/collection.js.map +1 -1
- package/lib/constants.js +9 -1
- package/lib/constants.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +231 -285
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +11 -19
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/change_stream_cursor.js +12 -14
- package/lib/cursor/change_stream_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +64 -84
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/list_collections_cursor.js +0 -1
- package/lib/cursor/list_collections_cursor.js.map +1 -1
- package/lib/cursor/list_indexes_cursor.js +0 -1
- package/lib/cursor/list_indexes_cursor.js.map +1 -1
- package/lib/cursor/run_command_cursor.js +4 -6
- package/lib/cursor/run_command_cursor.js.map +1 -1
- package/lib/error.js +10 -23
- package/lib/error.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/operations/aggregate.js +2 -2
- package/lib/operations/aggregate.js.map +1 -1
- package/lib/operations/bulk_write.js +1 -2
- package/lib/operations/bulk_write.js.map +1 -1
- package/lib/operations/command.js +2 -3
- package/lib/operations/command.js.map +1 -1
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find.js +2 -1
- package/lib/operations/find.js.map +1 -1
- package/lib/operations/get_more.js +1 -1
- package/lib/operations/get_more.js.map +1 -1
- package/lib/operations/indexes.js +2 -1
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/list_collections.js +2 -1
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/run_command.js +1 -1
- package/lib/operations/run_command.js.map +1 -1
- package/lib/operations/update.js +2 -1
- package/lib/operations/update.js.map +1 -1
- package/lib/sdam/server.js +7 -2
- package/lib/sdam/server.js.map +1 -1
- package/lib/sessions.js +1 -1
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +45 -1
- package/lib/utils.js.map +1 -1
- package/lib/write_concern.js +17 -1
- package/lib/write_concern.js.map +1 -1
- package/mongodb.d.ts +187 -150
- package/package.json +2 -2
- package/src/bson.ts +1 -0
- package/src/client-side-encryption/auto_encrypter.ts +9 -70
- package/src/client-side-encryption/client_encryption.ts +33 -19
- package/src/client-side-encryption/providers/index.ts +118 -92
- package/src/client-side-encryption/state_machine.ts +22 -18
- package/src/cmap/connection.ts +46 -50
- package/src/cmap/wire_protocol/on_demand/document.ts +13 -6
- package/src/cmap/wire_protocol/responses.ts +140 -45
- package/src/collection.ts +25 -5
- package/src/constants.ts +9 -0
- package/src/cursor/abstract_cursor.ts +280 -373
- package/src/cursor/aggregation_cursor.ts +24 -33
- package/src/cursor/change_stream_cursor.ts +31 -48
- package/src/cursor/find_cursor.ts +77 -92
- package/src/cursor/list_collections_cursor.ts +3 -4
- package/src/cursor/list_indexes_cursor.ts +3 -4
- package/src/cursor/run_command_cursor.ts +13 -19
- package/src/error.ts +20 -30
- package/src/index.ts +19 -10
- package/src/operations/aggregate.ts +12 -5
- package/src/operations/bulk_write.ts +1 -2
- package/src/operations/command.ts +17 -3
- package/src/operations/delete.ts +2 -2
- package/src/operations/execute_operation.ts +0 -13
- package/src/operations/find.ts +7 -3
- package/src/operations/find_and_modify.ts +1 -1
- package/src/operations/get_more.ts +6 -10
- package/src/operations/indexes.ts +7 -3
- package/src/operations/list_collections.ts +8 -3
- package/src/operations/run_command.ts +16 -6
- package/src/operations/update.ts +2 -1
- package/src/sdam/server.ts +7 -2
- package/src/sessions.ts +1 -1
- package/src/utils.ts +52 -2
- package/src/write_concern.ts +18 -0
- package/lib/operations/count_documents.js +0 -31
- package/lib/operations/count_documents.js.map +0 -1
- package/src/operations/count_documents.ts +0 -46
|
@@ -1,52 +1,43 @@
|
|
|
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
|
-
type AnyError,
|
|
7
6
|
MongoAPIError,
|
|
8
7
|
MongoCursorExhaustedError,
|
|
9
8
|
MongoCursorInUseError,
|
|
10
9
|
MongoInvalidArgumentError,
|
|
11
|
-
MongoNetworkError,
|
|
12
10
|
MongoRuntimeError,
|
|
13
11
|
MongoTailableCursorError
|
|
14
12
|
} from '../error';
|
|
15
13
|
import type { MongoClient } from '../mongo_client';
|
|
16
|
-
import {
|
|
17
|
-
import { executeOperation
|
|
14
|
+
import { TypedEventEmitter } from '../mongo_types';
|
|
15
|
+
import { executeOperation } from '../operations/execute_operation';
|
|
18
16
|
import { GetMoreOperation } from '../operations/get_more';
|
|
19
17
|
import { KillCursorsOperation } from '../operations/kill_cursors';
|
|
20
18
|
import { ReadConcern, type ReadConcernLike } from '../read_concern';
|
|
21
19
|
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
|
|
22
20
|
import type { Server } from '../sdam/server';
|
|
23
21
|
import { ClientSession, maybeClearPinnedConnection } from '../sessions';
|
|
24
|
-
import {
|
|
22
|
+
import { type MongoDBNamespace, squashError } from '../utils';
|
|
25
23
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const kInitialized = Symbol('initialized');
|
|
44
|
-
/** @internal */
|
|
45
|
-
const kClosed = Symbol('closed');
|
|
46
|
-
/** @internal */
|
|
47
|
-
const kKilled = Symbol('killed');
|
|
48
|
-
/** @internal */
|
|
49
|
-
const kInit = Symbol('kInit');
|
|
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
|
+
}
|
|
50
41
|
|
|
51
42
|
/** @public */
|
|
52
43
|
export const CURSOR_FLAGS = [
|
|
@@ -137,39 +128,33 @@ export abstract class AbstractCursor<
|
|
|
137
128
|
CursorEvents extends AbstractCursorEvents = AbstractCursorEvents
|
|
138
129
|
> extends TypedEventEmitter<CursorEvents> {
|
|
139
130
|
/** @internal */
|
|
140
|
-
|
|
131
|
+
private cursorId: Long | null;
|
|
141
132
|
/** @internal */
|
|
142
|
-
|
|
133
|
+
private cursorSession: ClientSession;
|
|
143
134
|
/** @internal */
|
|
144
|
-
|
|
135
|
+
private selectedServer?: Server;
|
|
145
136
|
/** @internal */
|
|
146
|
-
|
|
137
|
+
private cursorNamespace: MongoDBNamespace;
|
|
147
138
|
/** @internal */
|
|
148
|
-
|
|
149
|
-
length: number;
|
|
150
|
-
shift(bsonOptions?: any): TSchema | null;
|
|
151
|
-
clear(): void;
|
|
152
|
-
pushMany(many: Iterable<TSchema>): void;
|
|
153
|
-
push(item: TSchema): void;
|
|
154
|
-
};
|
|
139
|
+
private documents: CursorResponse | null = null;
|
|
155
140
|
/** @internal */
|
|
156
|
-
|
|
141
|
+
private cursorClient: MongoClient;
|
|
157
142
|
/** @internal */
|
|
158
|
-
|
|
143
|
+
private transform?: (doc: TSchema) => any;
|
|
159
144
|
/** @internal */
|
|
160
|
-
|
|
145
|
+
private initialized: boolean;
|
|
161
146
|
/** @internal */
|
|
162
|
-
|
|
147
|
+
private isClosed: boolean;
|
|
163
148
|
/** @internal */
|
|
164
|
-
|
|
149
|
+
private isKilled: boolean;
|
|
165
150
|
/** @internal */
|
|
166
|
-
|
|
151
|
+
protected readonly cursorOptions: InternalAbstractCursorOptions;
|
|
167
152
|
|
|
168
153
|
/** @event */
|
|
169
154
|
static readonly CLOSE = 'close' as const;
|
|
170
155
|
|
|
171
156
|
/** @internal */
|
|
172
|
-
constructor(
|
|
157
|
+
protected constructor(
|
|
173
158
|
client: MongoClient,
|
|
174
159
|
namespace: MongoDBNamespace,
|
|
175
160
|
options: AbstractCursorOptions = {}
|
|
@@ -179,121 +164,132 @@ export abstract class AbstractCursor<
|
|
|
179
164
|
if (!client.s.isMongoClient) {
|
|
180
165
|
throw new MongoRuntimeError('Cursor must be constructed with MongoClient');
|
|
181
166
|
}
|
|
182
|
-
this
|
|
183
|
-
this
|
|
184
|
-
this
|
|
185
|
-
this
|
|
186
|
-
this
|
|
187
|
-
this
|
|
188
|
-
this
|
|
189
|
-
this[kOptions] = {
|
|
167
|
+
this.cursorClient = client;
|
|
168
|
+
this.cursorNamespace = namespace;
|
|
169
|
+
this.cursorId = null;
|
|
170
|
+
this.initialized = false;
|
|
171
|
+
this.isClosed = false;
|
|
172
|
+
this.isKilled = false;
|
|
173
|
+
this.cursorOptions = {
|
|
190
174
|
readPreference:
|
|
191
175
|
options.readPreference && options.readPreference instanceof ReadPreference
|
|
192
176
|
? options.readPreference
|
|
193
177
|
: ReadPreference.primary,
|
|
194
178
|
...pluckBSONSerializeOptions(options)
|
|
195
179
|
};
|
|
196
|
-
this
|
|
180
|
+
this.cursorOptions.timeoutMS = options.timeoutMS;
|
|
197
181
|
|
|
198
182
|
const readConcern = ReadConcern.fromOptions(options);
|
|
199
183
|
if (readConcern) {
|
|
200
|
-
this
|
|
184
|
+
this.cursorOptions.readConcern = readConcern;
|
|
201
185
|
}
|
|
202
186
|
|
|
203
187
|
if (typeof options.batchSize === 'number') {
|
|
204
|
-
this
|
|
188
|
+
this.cursorOptions.batchSize = options.batchSize;
|
|
205
189
|
}
|
|
206
190
|
|
|
207
191
|
// we check for undefined specifically here to allow falsy values
|
|
208
192
|
// eslint-disable-next-line no-restricted-syntax
|
|
209
193
|
if (options.comment !== undefined) {
|
|
210
|
-
this
|
|
194
|
+
this.cursorOptions.comment = options.comment;
|
|
211
195
|
}
|
|
212
196
|
|
|
213
197
|
if (typeof options.maxTimeMS === 'number') {
|
|
214
|
-
this
|
|
198
|
+
this.cursorOptions.maxTimeMS = options.maxTimeMS;
|
|
215
199
|
}
|
|
216
200
|
|
|
217
201
|
if (typeof options.maxAwaitTimeMS === 'number') {
|
|
218
|
-
this
|
|
202
|
+
this.cursorOptions.maxAwaitTimeMS = options.maxAwaitTimeMS;
|
|
219
203
|
}
|
|
220
204
|
|
|
221
205
|
if (options.session instanceof ClientSession) {
|
|
222
|
-
this
|
|
206
|
+
this.cursorSession = options.session;
|
|
223
207
|
} else {
|
|
224
|
-
this
|
|
208
|
+
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
|
|
225
209
|
}
|
|
226
210
|
}
|
|
227
211
|
|
|
212
|
+
/**
|
|
213
|
+
* The cursor has no id until it receives a response from the initial cursor creating command.
|
|
214
|
+
*
|
|
215
|
+
* It is non-zero for as long as the database has an open cursor.
|
|
216
|
+
*
|
|
217
|
+
* The initiating command may receive a zero id if the entire result is in the `firstBatch`.
|
|
218
|
+
*/
|
|
228
219
|
get id(): Long | undefined {
|
|
229
|
-
return this
|
|
220
|
+
return this.cursorId ?? undefined;
|
|
230
221
|
}
|
|
231
222
|
|
|
232
223
|
/** @internal */
|
|
233
224
|
get isDead() {
|
|
234
|
-
return (this
|
|
225
|
+
return (this.cursorId?.isZero() ?? false) || this.isClosed || this.isKilled;
|
|
235
226
|
}
|
|
236
227
|
|
|
237
228
|
/** @internal */
|
|
238
229
|
get client(): MongoClient {
|
|
239
|
-
return this
|
|
230
|
+
return this.cursorClient;
|
|
240
231
|
}
|
|
241
232
|
|
|
242
233
|
/** @internal */
|
|
243
234
|
get server(): Server | undefined {
|
|
244
|
-
return this
|
|
235
|
+
return this.selectedServer;
|
|
245
236
|
}
|
|
246
237
|
|
|
247
238
|
get namespace(): MongoDBNamespace {
|
|
248
|
-
return this
|
|
239
|
+
return this.cursorNamespace;
|
|
249
240
|
}
|
|
250
241
|
|
|
251
242
|
get readPreference(): ReadPreference {
|
|
252
|
-
return this
|
|
243
|
+
return this.cursorOptions.readPreference;
|
|
253
244
|
}
|
|
254
245
|
|
|
255
246
|
get readConcern(): ReadConcern | undefined {
|
|
256
|
-
return this
|
|
247
|
+
return this.cursorOptions.readConcern;
|
|
257
248
|
}
|
|
258
249
|
|
|
259
250
|
/** @internal */
|
|
260
251
|
get session(): ClientSession {
|
|
261
|
-
return this
|
|
252
|
+
return this.cursorSession;
|
|
262
253
|
}
|
|
263
254
|
|
|
264
255
|
set session(clientSession: ClientSession) {
|
|
265
|
-
this
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** @internal */
|
|
269
|
-
get cursorOptions(): InternalAbstractCursorOptions {
|
|
270
|
-
return this[kOptions];
|
|
256
|
+
this.cursorSession = clientSession;
|
|
271
257
|
}
|
|
272
258
|
|
|
259
|
+
/**
|
|
260
|
+
* The cursor is closed and all remaining locally buffered documents have been iterated.
|
|
261
|
+
*/
|
|
273
262
|
get closed(): boolean {
|
|
274
|
-
return this
|
|
263
|
+
return this.isClosed && (this.documents?.length ?? 0) === 0;
|
|
275
264
|
}
|
|
276
265
|
|
|
266
|
+
/**
|
|
267
|
+
* A `killCursors` command was attempted on this cursor.
|
|
268
|
+
* This is performed if the cursor id is non zero.
|
|
269
|
+
*/
|
|
277
270
|
get killed(): boolean {
|
|
278
|
-
return this
|
|
271
|
+
return this.isKilled;
|
|
279
272
|
}
|
|
280
273
|
|
|
281
274
|
get loadBalanced(): boolean {
|
|
282
|
-
return !!this
|
|
275
|
+
return !!this.cursorClient.topology?.loadBalanced;
|
|
283
276
|
}
|
|
284
277
|
|
|
285
278
|
/** Returns current buffered documents length */
|
|
286
279
|
bufferedCount(): number {
|
|
287
|
-
return this
|
|
280
|
+
return this.documents?.length ?? 0;
|
|
288
281
|
}
|
|
289
282
|
|
|
290
283
|
/** Returns current buffered documents */
|
|
291
284
|
readBufferedDocuments(number?: number): TSchema[] {
|
|
292
285
|
const bufferedDocs: TSchema[] = [];
|
|
293
|
-
const documentsToRead = Math.min(
|
|
286
|
+
const documentsToRead = Math.min(
|
|
287
|
+
number ?? this.documents?.length ?? 0,
|
|
288
|
+
this.documents?.length ?? 0
|
|
289
|
+
);
|
|
294
290
|
|
|
295
291
|
for (let count = 0; count < documentsToRead; count++) {
|
|
296
|
-
const document = this
|
|
292
|
+
const document = this.documents?.shift(this.cursorOptions);
|
|
297
293
|
if (document != null) {
|
|
298
294
|
bufferedDocs.push(document);
|
|
299
295
|
}
|
|
@@ -301,46 +297,38 @@ export abstract class AbstractCursor<
|
|
|
301
297
|
|
|
302
298
|
return bufferedDocs;
|
|
303
299
|
}
|
|
304
|
-
|
|
305
300
|
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
|
|
306
|
-
if (this.
|
|
301
|
+
if (this.isClosed) {
|
|
307
302
|
return;
|
|
308
303
|
}
|
|
309
304
|
|
|
310
305
|
try {
|
|
311
306
|
while (true) {
|
|
307
|
+
if (this.isKilled) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (this.closed) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (this.cursorId != null && this.isDead && (this.documents?.length ?? 0) === 0) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
312
319
|
const document = await this.next();
|
|
313
320
|
|
|
314
|
-
// Intentional strict null check, because users can map cursors to falsey values.
|
|
315
|
-
// We allow mapping to all values except for null.
|
|
316
321
|
// eslint-disable-next-line no-restricted-syntax
|
|
317
322
|
if (document === null) {
|
|
318
|
-
|
|
319
|
-
const message =
|
|
320
|
-
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
await cleanupCursor(this, { needsToEmitClosed: true });
|
|
324
|
-
} catch (error) {
|
|
325
|
-
squashError(error);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
throw new MongoAPIError(message);
|
|
329
|
-
}
|
|
330
|
-
break;
|
|
323
|
+
return;
|
|
331
324
|
}
|
|
332
325
|
|
|
333
326
|
yield document;
|
|
334
|
-
|
|
335
|
-
if (this[kId] === Long.ZERO) {
|
|
336
|
-
// Cursor exhausted
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
327
|
}
|
|
340
328
|
} finally {
|
|
341
329
|
// Only close the cursor if it has not already been closed. This finally clause handles
|
|
342
330
|
// the case when a user would break out of a for await of loop early.
|
|
343
|
-
if (!this.
|
|
331
|
+
if (!this.isClosed) {
|
|
344
332
|
try {
|
|
345
333
|
await this.close();
|
|
346
334
|
} catch (error) {
|
|
@@ -381,35 +369,61 @@ export abstract class AbstractCursor<
|
|
|
381
369
|
}
|
|
382
370
|
|
|
383
371
|
async hasNext(): Promise<boolean> {
|
|
384
|
-
if (this
|
|
372
|
+
if (this.cursorId === Long.ZERO) {
|
|
385
373
|
return false;
|
|
386
374
|
}
|
|
387
375
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
376
|
+
do {
|
|
377
|
+
if ((this.documents?.length ?? 0) !== 0) {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
await this.fetchBatch();
|
|
381
|
+
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
|
|
391
382
|
|
|
392
|
-
return
|
|
383
|
+
return false;
|
|
393
384
|
}
|
|
394
385
|
|
|
395
386
|
/** Get the next available document from the cursor, returns null if no more documents are available. */
|
|
396
387
|
async next(): Promise<TSchema | null> {
|
|
397
|
-
if (this
|
|
388
|
+
if (this.cursorId === Long.ZERO) {
|
|
398
389
|
throw new MongoCursorExhaustedError();
|
|
399
390
|
}
|
|
400
391
|
|
|
401
|
-
|
|
392
|
+
do {
|
|
393
|
+
const doc = this.documents?.shift(this.cursorOptions);
|
|
394
|
+
if (doc != null) {
|
|
395
|
+
if (this.transform != null) return await this.transformDocument(doc);
|
|
396
|
+
return doc;
|
|
397
|
+
}
|
|
398
|
+
await this.fetchBatch();
|
|
399
|
+
} while (!this.isDead || (this.documents?.length ?? 0) !== 0);
|
|
400
|
+
|
|
401
|
+
return null;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
/**
|
|
405
405
|
* Try to get the next available document from the cursor or `null` if an empty batch is returned
|
|
406
406
|
*/
|
|
407
407
|
async tryNext(): Promise<TSchema | null> {
|
|
408
|
-
if (this
|
|
408
|
+
if (this.cursorId === Long.ZERO) {
|
|
409
409
|
throw new MongoCursorExhaustedError();
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
|
|
412
|
+
let doc = this.documents?.shift(this.cursorOptions);
|
|
413
|
+
if (doc != null) {
|
|
414
|
+
if (this.transform != null) return await this.transformDocument(doc);
|
|
415
|
+
return doc;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await this.fetchBatch();
|
|
419
|
+
|
|
420
|
+
doc = this.documents?.shift(this.cursorOptions);
|
|
421
|
+
if (doc != null) {
|
|
422
|
+
if (this.transform != null) return await this.transformDocument(doc);
|
|
423
|
+
return doc;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return null;
|
|
413
427
|
}
|
|
414
428
|
|
|
415
429
|
/**
|
|
@@ -433,9 +447,7 @@ export abstract class AbstractCursor<
|
|
|
433
447
|
}
|
|
434
448
|
|
|
435
449
|
async close(): Promise<void> {
|
|
436
|
-
|
|
437
|
-
this[kClosed] = true;
|
|
438
|
-
await cleanupCursor(this, { needsToEmitClosed });
|
|
450
|
+
await this.cleanup();
|
|
439
451
|
}
|
|
440
452
|
|
|
441
453
|
/**
|
|
@@ -459,7 +471,7 @@ export abstract class AbstractCursor<
|
|
|
459
471
|
* @param value - The flag boolean value.
|
|
460
472
|
*/
|
|
461
473
|
addCursorFlag(flag: CursorFlag, value: boolean): this {
|
|
462
|
-
|
|
474
|
+
this.throwIfInitialized();
|
|
463
475
|
if (!CURSOR_FLAGS.includes(flag)) {
|
|
464
476
|
throw new MongoInvalidArgumentError(`Flag ${flag} is not one of ${CURSOR_FLAGS}`);
|
|
465
477
|
}
|
|
@@ -468,7 +480,7 @@ export abstract class AbstractCursor<
|
|
|
468
480
|
throw new MongoInvalidArgumentError(`Flag ${flag} must be a boolean value`);
|
|
469
481
|
}
|
|
470
482
|
|
|
471
|
-
this[
|
|
483
|
+
this.cursorOptions[flag] = value;
|
|
472
484
|
return this;
|
|
473
485
|
}
|
|
474
486
|
|
|
@@ -515,14 +527,14 @@ export abstract class AbstractCursor<
|
|
|
515
527
|
* @param transform - The mapping transformation method.
|
|
516
528
|
*/
|
|
517
529
|
map<T = any>(transform: (doc: TSchema) => T): AbstractCursor<T> {
|
|
518
|
-
|
|
519
|
-
const oldTransform = this
|
|
530
|
+
this.throwIfInitialized();
|
|
531
|
+
const oldTransform = this.transform;
|
|
520
532
|
if (oldTransform) {
|
|
521
|
-
this
|
|
533
|
+
this.transform = doc => {
|
|
522
534
|
return transform(oldTransform(doc));
|
|
523
535
|
};
|
|
524
536
|
} else {
|
|
525
|
-
this
|
|
537
|
+
this.transform = transform;
|
|
526
538
|
}
|
|
527
539
|
|
|
528
540
|
return this as unknown as AbstractCursor<T>;
|
|
@@ -534,11 +546,11 @@ export abstract class AbstractCursor<
|
|
|
534
546
|
* @param readPreference - The new read preference for the cursor.
|
|
535
547
|
*/
|
|
536
548
|
withReadPreference(readPreference: ReadPreferenceLike): this {
|
|
537
|
-
|
|
549
|
+
this.throwIfInitialized();
|
|
538
550
|
if (readPreference instanceof ReadPreference) {
|
|
539
|
-
this
|
|
551
|
+
this.cursorOptions.readPreference = readPreference;
|
|
540
552
|
} else if (typeof readPreference === 'string') {
|
|
541
|
-
this
|
|
553
|
+
this.cursorOptions.readPreference = ReadPreference.fromString(readPreference);
|
|
542
554
|
} else {
|
|
543
555
|
throw new MongoInvalidArgumentError(`Invalid read preference: ${readPreference}`);
|
|
544
556
|
}
|
|
@@ -552,10 +564,10 @@ export abstract class AbstractCursor<
|
|
|
552
564
|
* @param readPreference - The new read preference for the cursor.
|
|
553
565
|
*/
|
|
554
566
|
withReadConcern(readConcern: ReadConcernLike): this {
|
|
555
|
-
|
|
567
|
+
this.throwIfInitialized();
|
|
556
568
|
const resolvedReadConcern = ReadConcern.fromOptions({ readConcern });
|
|
557
569
|
if (resolvedReadConcern) {
|
|
558
|
-
this
|
|
570
|
+
this.cursorOptions.readConcern = resolvedReadConcern;
|
|
559
571
|
}
|
|
560
572
|
|
|
561
573
|
return this;
|
|
@@ -567,12 +579,12 @@ export abstract class AbstractCursor<
|
|
|
567
579
|
* @param value - Number of milliseconds to wait before aborting the query.
|
|
568
580
|
*/
|
|
569
581
|
maxTimeMS(value: number): this {
|
|
570
|
-
|
|
582
|
+
this.throwIfInitialized();
|
|
571
583
|
if (typeof value !== 'number') {
|
|
572
584
|
throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number');
|
|
573
585
|
}
|
|
574
586
|
|
|
575
|
-
this
|
|
587
|
+
this.cursorOptions.maxTimeMS = value;
|
|
576
588
|
return this;
|
|
577
589
|
}
|
|
578
590
|
|
|
@@ -582,8 +594,8 @@ export abstract class AbstractCursor<
|
|
|
582
594
|
* @param value - The number of documents to return per batch. See {@link https://www.mongodb.com/docs/manual/reference/command/find/|find command documentation}.
|
|
583
595
|
*/
|
|
584
596
|
batchSize(value: number): this {
|
|
585
|
-
|
|
586
|
-
if (this
|
|
597
|
+
this.throwIfInitialized();
|
|
598
|
+
if (this.cursorOptions.tailable) {
|
|
587
599
|
throw new MongoTailableCursorError('Tailable cursor does not support batchSize');
|
|
588
600
|
}
|
|
589
601
|
|
|
@@ -591,7 +603,7 @@ export abstract class AbstractCursor<
|
|
|
591
603
|
throw new MongoInvalidArgumentError('Operation "batchSize" requires an integer');
|
|
592
604
|
}
|
|
593
605
|
|
|
594
|
-
this
|
|
606
|
+
this.cursorOptions.batchSize = value;
|
|
595
607
|
return this;
|
|
596
608
|
}
|
|
597
609
|
|
|
@@ -601,17 +613,17 @@ export abstract class AbstractCursor<
|
|
|
601
613
|
* if the resultant data has already been retrieved by this cursor.
|
|
602
614
|
*/
|
|
603
615
|
rewind(): void {
|
|
604
|
-
if (!this
|
|
616
|
+
if (!this.initialized) {
|
|
605
617
|
return;
|
|
606
618
|
}
|
|
607
619
|
|
|
608
|
-
this
|
|
609
|
-
this
|
|
610
|
-
this
|
|
611
|
-
this
|
|
612
|
-
this
|
|
620
|
+
this.cursorId = null;
|
|
621
|
+
this.documents?.clear();
|
|
622
|
+
this.isClosed = false;
|
|
623
|
+
this.isKilled = false;
|
|
624
|
+
this.initialized = false;
|
|
613
625
|
|
|
614
|
-
const session = this
|
|
626
|
+
const session = this.cursorSession;
|
|
615
627
|
if (session) {
|
|
616
628
|
// We only want to end this session if we created it, and it hasn't ended yet
|
|
617
629
|
if (session.explicit === false) {
|
|
@@ -619,7 +631,7 @@ export abstract class AbstractCursor<
|
|
|
619
631
|
// eslint-disable-next-line github/no-then
|
|
620
632
|
session.endSession().then(undefined, squashError);
|
|
621
633
|
}
|
|
622
|
-
this
|
|
634
|
+
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
|
|
623
635
|
}
|
|
624
636
|
}
|
|
625
637
|
}
|
|
@@ -630,19 +642,34 @@ export abstract class AbstractCursor<
|
|
|
630
642
|
abstract clone(): AbstractCursor<TSchema>;
|
|
631
643
|
|
|
632
644
|
/** @internal */
|
|
633
|
-
protected abstract _initialize(
|
|
645
|
+
protected abstract _initialize(
|
|
646
|
+
session: ClientSession | undefined
|
|
647
|
+
): Promise<InitialCursorResponse>;
|
|
634
648
|
|
|
635
649
|
/** @internal */
|
|
636
|
-
async getMore(batchSize: number
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
650
|
+
async getMore(batchSize: number): Promise<CursorResponse> {
|
|
651
|
+
if (this.cursorId == null) {
|
|
652
|
+
throw new MongoRuntimeError(
|
|
653
|
+
'Unexpected null cursor id. A cursor creating command should have set this'
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (this.selectedServer == null) {
|
|
657
|
+
throw new MongoRuntimeError(
|
|
658
|
+
'Unexpected null selectedServer. A cursor creating command should have set this'
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
const getMoreOperation = new GetMoreOperation(
|
|
662
|
+
this.cursorNamespace,
|
|
663
|
+
this.cursorId,
|
|
664
|
+
this.selectedServer,
|
|
665
|
+
{
|
|
666
|
+
...this.cursorOptions,
|
|
667
|
+
session: this.cursorSession,
|
|
668
|
+
batchSize
|
|
669
|
+
}
|
|
670
|
+
);
|
|
644
671
|
|
|
645
|
-
return await executeOperation(this
|
|
672
|
+
return await executeOperation(this.cursorClient, getMoreOperation);
|
|
646
673
|
}
|
|
647
674
|
|
|
648
675
|
/**
|
|
@@ -652,169 +679,60 @@ export abstract class AbstractCursor<
|
|
|
652
679
|
* operation. We cannot refactor to use the abstract _initialize method without
|
|
653
680
|
* a significant refactor.
|
|
654
681
|
*/
|
|
655
|
-
async
|
|
682
|
+
private async cursorInit(): Promise<void> {
|
|
656
683
|
try {
|
|
657
|
-
const state = await this._initialize(this
|
|
684
|
+
const state = await this._initialize(this.cursorSession);
|
|
658
685
|
const response = state.response;
|
|
659
|
-
this
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
} else if (response.cursor) {
|
|
665
|
-
// TODO(NODE-2674): Preserve int64 sent from MongoDB
|
|
666
|
-
this[kId] =
|
|
667
|
-
typeof response.cursor.id === 'number'
|
|
668
|
-
? Long.fromNumber(response.cursor.id)
|
|
669
|
-
: typeof response.cursor.id === 'bigint'
|
|
670
|
-
? Long.fromBigInt(response.cursor.id)
|
|
671
|
-
: response.cursor.id;
|
|
672
|
-
|
|
673
|
-
if (response.cursor.ns) {
|
|
674
|
-
this[kNamespace] = ns(response.cursor.ns);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
this[kDocuments].pushMany(response.cursor.firstBatch);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// When server responses return without a cursor document, we close this cursor
|
|
681
|
-
// and return the raw server response. This is often the case for explain commands
|
|
682
|
-
// for example
|
|
683
|
-
if (this[kId] == null) {
|
|
684
|
-
this[kId] = Long.ZERO;
|
|
685
|
-
// TODO(NODE-3286): ExecutionResult needs to accept a generic parameter
|
|
686
|
-
this[kDocuments].push(state.response as TODO_NODE_3286);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// the cursor is now initialized, even if it is dead
|
|
690
|
-
this[kInitialized] = true;
|
|
686
|
+
this.selectedServer = state.server;
|
|
687
|
+
this.cursorId = response.id;
|
|
688
|
+
this.cursorNamespace = response.ns ?? this.namespace;
|
|
689
|
+
this.documents = response;
|
|
690
|
+
this.initialized = true; // the cursor is now initialized, even if it is dead
|
|
691
691
|
} catch (error) {
|
|
692
692
|
// the cursor is now initialized, even if an error occurred
|
|
693
|
-
this
|
|
694
|
-
await
|
|
693
|
+
this.initialized = true;
|
|
694
|
+
await this.cleanup(error);
|
|
695
695
|
throw error;
|
|
696
696
|
}
|
|
697
697
|
|
|
698
698
|
if (this.isDead) {
|
|
699
|
-
await
|
|
699
|
+
await this.cleanup();
|
|
700
700
|
}
|
|
701
701
|
|
|
702
702
|
return;
|
|
703
703
|
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
* @param cursor - the cursor on which to call `next`
|
|
708
|
-
* @param blocking - a boolean indicating whether or not the cursor should `block` until data
|
|
709
|
-
* is available. Generally, this flag is set to `false` because if the getMore returns no documents,
|
|
710
|
-
* the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
|
|
711
|
-
* `tryNext`, for example) blocking is necessary because a getMore returning no documents does
|
|
712
|
-
* not indicate the end of the cursor.
|
|
713
|
-
* @param transform - if true, the cursor's transform function is applied to the result document (if the transform exists)
|
|
714
|
-
* @returns the next document in the cursor, or `null`. When `blocking` is `true`, a `null` document means
|
|
715
|
-
* the cursor has been exhausted. Otherwise, it means that there is no document available in the cursor's buffer.
|
|
716
|
-
*/
|
|
717
|
-
async function next<T>(
|
|
718
|
-
cursor: AbstractCursor<T>,
|
|
719
|
-
{
|
|
720
|
-
blocking,
|
|
721
|
-
transform,
|
|
722
|
-
shift
|
|
723
|
-
}: {
|
|
724
|
-
blocking: boolean;
|
|
725
|
-
transform: boolean;
|
|
726
|
-
shift: false;
|
|
727
|
-
}
|
|
728
|
-
): Promise<boolean>;
|
|
729
|
-
|
|
730
|
-
async function next<T>(
|
|
731
|
-
cursor: AbstractCursor<T>,
|
|
732
|
-
{
|
|
733
|
-
blocking,
|
|
734
|
-
transform,
|
|
735
|
-
shift
|
|
736
|
-
}: {
|
|
737
|
-
blocking: boolean;
|
|
738
|
-
transform: boolean;
|
|
739
|
-
shift: true;
|
|
740
|
-
}
|
|
741
|
-
): Promise<T | null>;
|
|
742
|
-
|
|
743
|
-
async function next<T>(
|
|
744
|
-
cursor: AbstractCursor<T>,
|
|
745
|
-
{
|
|
746
|
-
blocking,
|
|
747
|
-
transform,
|
|
748
|
-
shift
|
|
749
|
-
}: {
|
|
750
|
-
blocking: boolean;
|
|
751
|
-
transform: boolean;
|
|
752
|
-
shift: boolean;
|
|
753
|
-
}
|
|
754
|
-
): Promise<boolean | T | null> {
|
|
755
|
-
if (cursor.closed) {
|
|
756
|
-
if (!shift) return false;
|
|
757
|
-
return null;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
do {
|
|
761
|
-
if (cursor[kId] == null) {
|
|
762
|
-
// All cursors must operate within a session, one must be made implicitly if not explicitly provided
|
|
763
|
-
await cursor[kInit]();
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (cursor[kDocuments].length !== 0) {
|
|
767
|
-
if (!shift) return true;
|
|
768
|
-
const doc = cursor[kDocuments].shift(cursor[kOptions]);
|
|
769
|
-
|
|
770
|
-
if (doc != null && transform && cursor[kTransform]) {
|
|
771
|
-
try {
|
|
772
|
-
return cursor[kTransform](doc);
|
|
773
|
-
} catch (error) {
|
|
774
|
-
try {
|
|
775
|
-
await cleanupCursor(cursor, { error, needsToEmitClosed: true });
|
|
776
|
-
} catch (error) {
|
|
777
|
-
// `cleanupCursor` should never throw, squash and throw the original error
|
|
778
|
-
squashError(error);
|
|
779
|
-
}
|
|
780
|
-
throw error;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
704
|
|
|
784
|
-
|
|
705
|
+
/** @internal Attempt to obtain more documents */
|
|
706
|
+
private async fetchBatch(): Promise<void> {
|
|
707
|
+
if (this.isClosed) {
|
|
708
|
+
return;
|
|
785
709
|
}
|
|
786
710
|
|
|
787
|
-
if (
|
|
711
|
+
if (this.isDead) {
|
|
788
712
|
// if the cursor is dead, we clean it up
|
|
789
713
|
// cleanupCursor should never throw, but if it does it indicates a bug in the driver
|
|
790
714
|
// and we should surface the error
|
|
791
|
-
await
|
|
792
|
-
|
|
793
|
-
|
|
715
|
+
await this.cleanup();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (this.cursorId == null) {
|
|
720
|
+
await this.cursorInit();
|
|
721
|
+
// If the cursor died or returned documents, return
|
|
722
|
+
if ((this.documents?.length ?? 0) !== 0 || this.isDead) return;
|
|
723
|
+
// Otherwise, run a getMore
|
|
794
724
|
}
|
|
795
725
|
|
|
796
726
|
// otherwise need to call getMore
|
|
797
|
-
const batchSize =
|
|
727
|
+
const batchSize = this.cursorOptions.batchSize || 1000;
|
|
798
728
|
|
|
799
729
|
try {
|
|
800
|
-
const response = await
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
cursor[kDocuments] = response;
|
|
804
|
-
} else if (response) {
|
|
805
|
-
const cursorId =
|
|
806
|
-
typeof response.cursor.id === 'number'
|
|
807
|
-
? Long.fromNumber(response.cursor.id)
|
|
808
|
-
: typeof response.cursor.id === 'bigint'
|
|
809
|
-
? Long.fromBigInt(response.cursor.id)
|
|
810
|
-
: response.cursor.id;
|
|
811
|
-
|
|
812
|
-
cursor[kDocuments].pushMany(response.cursor.nextBatch);
|
|
813
|
-
cursor[kId] = cursorId;
|
|
814
|
-
}
|
|
730
|
+
const response = await this.getMore(batchSize);
|
|
731
|
+
this.cursorId = response.id;
|
|
732
|
+
this.documents = response;
|
|
815
733
|
} catch (error) {
|
|
816
734
|
try {
|
|
817
|
-
await
|
|
735
|
+
await this.cleanup(error);
|
|
818
736
|
} catch (error) {
|
|
819
737
|
// `cleanupCursor` should never throw, squash and throw the original error
|
|
820
738
|
squashError(error);
|
|
@@ -822,7 +740,7 @@ async function next<T>(
|
|
|
822
740
|
throw error;
|
|
823
741
|
}
|
|
824
742
|
|
|
825
|
-
if (
|
|
743
|
+
if (this.isDead) {
|
|
826
744
|
// If we successfully received a response from a cursor BUT the cursor indicates that it is exhausted,
|
|
827
745
|
// we intentionally clean up the cursor to release its session back into the pool before the cursor
|
|
828
746
|
// is iterated. This prevents a cursor that is exhausted on the server from holding
|
|
@@ -830,103 +748,87 @@ async function next<T>(
|
|
|
830
748
|
//
|
|
831
749
|
// cleanupCursorAsync should never throw, but if it does it indicates a bug in the driver
|
|
832
750
|
// and we should surface the error
|
|
833
|
-
await
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
if (cursor[kDocuments].length === 0 && blocking === false) {
|
|
837
|
-
if (!shift) return false;
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
} while (!cursor.isDead || cursor[kDocuments].length !== 0);
|
|
841
|
-
|
|
842
|
-
if (!shift) return false;
|
|
843
|
-
return null;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
async function cleanupCursor(
|
|
847
|
-
cursor: AbstractCursor,
|
|
848
|
-
options: { error?: AnyError | undefined; needsToEmitClosed?: boolean } | undefined
|
|
849
|
-
): Promise<void> {
|
|
850
|
-
const cursorId = cursor[kId];
|
|
851
|
-
const cursorNs = cursor[kNamespace];
|
|
852
|
-
const server = cursor[kServer];
|
|
853
|
-
const session = cursor[kSession];
|
|
854
|
-
const error = options?.error;
|
|
855
|
-
|
|
856
|
-
// Cursors only emit closed events once the client-side cursor has been exhausted fully or there
|
|
857
|
-
// was an error. Notably, when the server returns a cursor id of 0 and a non-empty batch, we
|
|
858
|
-
// cleanup the cursor but don't emit a `close` event.
|
|
859
|
-
const needsToEmitClosed = options?.needsToEmitClosed ?? cursor[kDocuments].length === 0;
|
|
860
|
-
|
|
861
|
-
if (error) {
|
|
862
|
-
if (cursor.loadBalanced && error instanceof MongoNetworkError) {
|
|
863
|
-
return await completeCleanup();
|
|
751
|
+
await this.cleanup();
|
|
864
752
|
}
|
|
865
753
|
}
|
|
866
754
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
755
|
+
/** @internal */
|
|
756
|
+
private async cleanup(error?: Error) {
|
|
757
|
+
this.isClosed = true;
|
|
758
|
+
const session = this.cursorSession;
|
|
759
|
+
try {
|
|
760
|
+
if (
|
|
761
|
+
!this.isKilled &&
|
|
762
|
+
this.cursorId &&
|
|
763
|
+
!this.cursorId.isZero() &&
|
|
764
|
+
this.cursorNamespace &&
|
|
765
|
+
this.selectedServer &&
|
|
766
|
+
!session.hasEnded
|
|
767
|
+
) {
|
|
768
|
+
this.isKilled = true;
|
|
769
|
+
const cursorId = this.cursorId;
|
|
770
|
+
this.cursorId = Long.ZERO;
|
|
771
|
+
await executeOperation(
|
|
772
|
+
this.cursorClient,
|
|
773
|
+
new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
|
|
774
|
+
session
|
|
775
|
+
})
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
} catch (error) {
|
|
779
|
+
squashError(error);
|
|
780
|
+
} finally {
|
|
781
|
+
if (session?.owner === this) {
|
|
876
782
|
await session.endSession({ error });
|
|
877
|
-
return;
|
|
878
783
|
}
|
|
879
|
-
|
|
880
|
-
if (!session.inTransaction()) {
|
|
784
|
+
if (!session?.inTransaction()) {
|
|
881
785
|
maybeClearPinnedConnection(session, { error });
|
|
882
786
|
}
|
|
883
|
-
}
|
|
884
787
|
|
|
885
|
-
|
|
788
|
+
this.emitClose();
|
|
789
|
+
}
|
|
886
790
|
}
|
|
887
791
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (!session.inTransaction()) {
|
|
900
|
-
maybeClearPinnedConnection(session, { error });
|
|
792
|
+
/** @internal */
|
|
793
|
+
private hasEmittedClose = false;
|
|
794
|
+
/** @internal */
|
|
795
|
+
private emitClose() {
|
|
796
|
+
try {
|
|
797
|
+
if (!this.hasEmittedClose && ((this.documents?.length ?? 0) === 0 || this.isClosed)) {
|
|
798
|
+
// @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.
|
|
799
|
+
this.emit('close');
|
|
901
800
|
}
|
|
801
|
+
} finally {
|
|
802
|
+
this.hasEmittedClose = true;
|
|
902
803
|
}
|
|
903
|
-
|
|
904
|
-
cursor.emit(AbstractCursor.CLOSE);
|
|
905
|
-
return;
|
|
906
804
|
}
|
|
907
805
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
return await completeCleanup();
|
|
912
|
-
}
|
|
806
|
+
/** @internal */
|
|
807
|
+
private async transformDocument(document: NonNullable<TSchema>): Promise<TSchema> {
|
|
808
|
+
if (this.transform == null) return document;
|
|
913
809
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
810
|
+
try {
|
|
811
|
+
const transformedDocument = this.transform(document);
|
|
812
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
813
|
+
if (transformedDocument === null) {
|
|
814
|
+
const TRANSFORM_TO_NULL_ERROR =
|
|
815
|
+
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
|
|
816
|
+
throw new MongoAPIError(TRANSFORM_TO_NULL_ERROR);
|
|
817
|
+
}
|
|
818
|
+
return transformedDocument;
|
|
819
|
+
} catch (transformError) {
|
|
820
|
+
try {
|
|
821
|
+
await this.close();
|
|
822
|
+
} catch (closeError) {
|
|
823
|
+
squashError(closeError);
|
|
824
|
+
}
|
|
825
|
+
throw transformError;
|
|
826
|
+
}
|
|
923
827
|
}
|
|
924
|
-
}
|
|
925
828
|
|
|
926
|
-
/** @internal */
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
throw new MongoCursorInUseError();
|
|
829
|
+
/** @internal */
|
|
830
|
+
protected throwIfInitialized() {
|
|
831
|
+
if (this.initialized) throw new MongoCursorInUseError();
|
|
930
832
|
}
|
|
931
833
|
}
|
|
932
834
|
|
|
@@ -960,8 +862,13 @@ class ReadableCursorStream extends Readable {
|
|
|
960
862
|
}
|
|
961
863
|
|
|
962
864
|
private _readNext() {
|
|
865
|
+
if (this._cursor.id === Long.ZERO) {
|
|
866
|
+
this.push(null);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
963
870
|
// eslint-disable-next-line github/no-then
|
|
964
|
-
|
|
871
|
+
this._cursor.next().then(
|
|
965
872
|
result => {
|
|
966
873
|
if (result == null) {
|
|
967
874
|
this.push(null);
|