mongodb 6.12.0-dev.20250125.sha.c1bcf0de → 6.12.0-dev.20250129.sha.907aac19
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/lib/beta.d.ts +55 -13
- package/lib/client-side-encryption/auto_encrypter.js +4 -2
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +4 -4
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/client-side-encryption/state_machine.js +56 -30
- package/lib/client-side-encryption/state_machine.js.map +1 -1
- package/lib/cmap/connection.js +28 -22
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +5 -0
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/wire_protocol/on_data.js +6 -1
- package/lib/cmap/wire_protocol/on_data.js.map +1 -1
- package/lib/collection.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +47 -18
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/aggregation_cursor.js +2 -1
- package/lib/cursor/aggregation_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +2 -1
- package/lib/cursor/find_cursor.js.map +1 -1
- package/lib/cursor/list_collections_cursor.js +2 -1
- package/lib/cursor/list_collections_cursor.js.map +1 -1
- package/lib/db.js +2 -1
- package/lib/db.js.map +1 -1
- package/lib/mongo_client.js +4 -0
- package/lib/mongo_client.js.map +1 -1
- package/lib/operations/execute_operation.js +7 -3
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/operation.js.map +1 -1
- package/lib/sdam/server.js +26 -16
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/topology.js +5 -0
- package/lib/sdam/topology.js.map +1 -1
- package/lib/utils.js +64 -7
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +55 -13
- package/package.json +1 -1
- package/src/client-side-encryption/auto_encrypter.ts +12 -8
- package/src/client-side-encryption/client_encryption.ts +6 -4
- package/src/client-side-encryption/state_machine.ts +80 -36
- package/src/cmap/connection.ts +37 -29
- package/src/cmap/connection_pool.ts +17 -3
- package/src/cmap/wire_protocol/on_data.ts +9 -2
- package/src/collection.ts +15 -8
- package/src/cursor/abstract_cursor.ts +71 -23
- package/src/cursor/aggregation_cursor.ts +5 -3
- package/src/cursor/find_cursor.ts +5 -3
- package/src/cursor/list_collections_cursor.ts +5 -3
- package/src/db.ts +11 -7
- package/src/index.ts +1 -0
- package/src/mongo_client.ts +13 -0
- package/src/mongo_types.ts +38 -0
- package/src/operations/execute_operation.ts +9 -4
- package/src/operations/list_collections.ts +4 -1
- package/src/operations/operation.ts +3 -2
- package/src/sdam/server.ts +31 -18
- package/src/sdam/topology.ts +10 -2
- package/src/utils.ts +79 -6
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
MongoTailableCursorError
|
|
13
13
|
} from '../error';
|
|
14
14
|
import type { MongoClient } from '../mongo_client';
|
|
15
|
-
import { TypedEventEmitter } from '../mongo_types';
|
|
15
|
+
import { type Abortable, TypedEventEmitter } from '../mongo_types';
|
|
16
16
|
import { executeOperation } from '../operations/execute_operation';
|
|
17
17
|
import { GetMoreOperation } from '../operations/get_more';
|
|
18
18
|
import { KillCursorsOperation } from '../operations/kill_cursors';
|
|
@@ -22,7 +22,13 @@ import { type AsyncDisposable, configureResourceManagement } from '../resource_m
|
|
|
22
22
|
import type { Server } from '../sdam/server';
|
|
23
23
|
import { ClientSession, maybeClearPinnedConnection } from '../sessions';
|
|
24
24
|
import { type CSOTTimeoutContext, type Timeout, TimeoutContext } from '../timeout';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
addAbortListener,
|
|
27
|
+
type Disposable,
|
|
28
|
+
kDispose,
|
|
29
|
+
type MongoDBNamespace,
|
|
30
|
+
squashError
|
|
31
|
+
} from '../utils';
|
|
26
32
|
|
|
27
33
|
/**
|
|
28
34
|
* @internal
|
|
@@ -61,6 +67,10 @@ export interface CursorStreamOptions {
|
|
|
61
67
|
/** @public */
|
|
62
68
|
export type CursorFlag = (typeof CURSOR_FLAGS)[number];
|
|
63
69
|
|
|
70
|
+
function removeActiveCursor(this: AbstractCursor) {
|
|
71
|
+
this.client.s.activeCursors.delete(this);
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
/**
|
|
65
75
|
* @public
|
|
66
76
|
* @experimental
|
|
@@ -247,12 +257,14 @@ export abstract class AbstractCursor<
|
|
|
247
257
|
|
|
248
258
|
/** @internal */
|
|
249
259
|
protected deserializationOptions: OnDemandDocumentDeserializeOptions;
|
|
260
|
+
protected signal: AbortSignal | undefined;
|
|
261
|
+
private abortListener: Disposable | undefined;
|
|
250
262
|
|
|
251
263
|
/** @internal */
|
|
252
264
|
protected constructor(
|
|
253
265
|
client: MongoClient,
|
|
254
266
|
namespace: MongoDBNamespace,
|
|
255
|
-
options: AbstractCursorOptions = {}
|
|
267
|
+
options: AbstractCursorOptions & Abortable = {}
|
|
256
268
|
) {
|
|
257
269
|
super();
|
|
258
270
|
|
|
@@ -352,6 +364,12 @@ export abstract class AbstractCursor<
|
|
|
352
364
|
};
|
|
353
365
|
|
|
354
366
|
this.timeoutContext = options.timeoutContext;
|
|
367
|
+
this.signal = options.signal;
|
|
368
|
+
this.abortListener = addAbortListener(
|
|
369
|
+
this.signal,
|
|
370
|
+
() => void this.close().then(undefined, squashError)
|
|
371
|
+
);
|
|
372
|
+
this.trackCursor();
|
|
355
373
|
}
|
|
356
374
|
|
|
357
375
|
/**
|
|
@@ -431,6 +449,14 @@ export abstract class AbstractCursor<
|
|
|
431
449
|
await this.close();
|
|
432
450
|
}
|
|
433
451
|
|
|
452
|
+
/** Adds cursor to client's tracking so it will be closed by MongoClient.close() */
|
|
453
|
+
private trackCursor() {
|
|
454
|
+
this.cursorClient.s.activeCursors.add(this);
|
|
455
|
+
if (!this.listeners('close').includes(removeActiveCursor)) {
|
|
456
|
+
this.once('close', removeActiveCursor);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
434
460
|
/** Returns current buffered documents length */
|
|
435
461
|
bufferedCount(): number {
|
|
436
462
|
return this.documents?.length ?? 0;
|
|
@@ -455,6 +481,8 @@ export abstract class AbstractCursor<
|
|
|
455
481
|
}
|
|
456
482
|
|
|
457
483
|
async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
|
|
484
|
+
this.signal?.throwIfAborted();
|
|
485
|
+
|
|
458
486
|
if (this.closed) {
|
|
459
487
|
return;
|
|
460
488
|
}
|
|
@@ -481,6 +509,8 @@ export abstract class AbstractCursor<
|
|
|
481
509
|
}
|
|
482
510
|
|
|
483
511
|
yield document;
|
|
512
|
+
|
|
513
|
+
this.signal?.throwIfAborted();
|
|
484
514
|
}
|
|
485
515
|
} finally {
|
|
486
516
|
// Only close the cursor if it has not already been closed. This finally clause handles
|
|
@@ -496,9 +526,16 @@ export abstract class AbstractCursor<
|
|
|
496
526
|
}
|
|
497
527
|
|
|
498
528
|
stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
|
|
529
|
+
const readable = new ReadableCursorStream(this);
|
|
530
|
+
const abortListener = addAbortListener(this.signal, function () {
|
|
531
|
+
readable.destroy(this.reason);
|
|
532
|
+
});
|
|
533
|
+
readable.once('end', () => {
|
|
534
|
+
abortListener?.[kDispose]();
|
|
535
|
+
});
|
|
536
|
+
|
|
499
537
|
if (options?.transform) {
|
|
500
538
|
const transform = options.transform;
|
|
501
|
-
const readable = new ReadableCursorStream(this);
|
|
502
539
|
|
|
503
540
|
const transformedStream = readable.pipe(
|
|
504
541
|
new Transform({
|
|
@@ -522,10 +559,12 @@ export abstract class AbstractCursor<
|
|
|
522
559
|
return transformedStream;
|
|
523
560
|
}
|
|
524
561
|
|
|
525
|
-
return
|
|
562
|
+
return readable;
|
|
526
563
|
}
|
|
527
564
|
|
|
528
565
|
async hasNext(): Promise<boolean> {
|
|
566
|
+
this.signal?.throwIfAborted();
|
|
567
|
+
|
|
529
568
|
if (this.cursorId === Long.ZERO) {
|
|
530
569
|
return false;
|
|
531
570
|
}
|
|
@@ -551,6 +590,8 @@ export abstract class AbstractCursor<
|
|
|
551
590
|
|
|
552
591
|
/** Get the next available document from the cursor, returns null if no more documents are available. */
|
|
553
592
|
async next(): Promise<TSchema | null> {
|
|
593
|
+
this.signal?.throwIfAborted();
|
|
594
|
+
|
|
554
595
|
if (this.cursorId === Long.ZERO) {
|
|
555
596
|
throw new MongoCursorExhaustedError();
|
|
556
597
|
}
|
|
@@ -581,6 +622,8 @@ export abstract class AbstractCursor<
|
|
|
581
622
|
* Try to get the next available document from the cursor or `null` if an empty batch is returned
|
|
582
623
|
*/
|
|
583
624
|
async tryNext(): Promise<TSchema | null> {
|
|
625
|
+
this.signal?.throwIfAborted();
|
|
626
|
+
|
|
584
627
|
if (this.cursorId === Long.ZERO) {
|
|
585
628
|
throw new MongoCursorExhaustedError();
|
|
586
629
|
}
|
|
@@ -620,6 +663,8 @@ export abstract class AbstractCursor<
|
|
|
620
663
|
* @deprecated - Will be removed in a future release. Use for await...of instead.
|
|
621
664
|
*/
|
|
622
665
|
async forEach(iterator: (doc: TSchema) => boolean | void): Promise<void> {
|
|
666
|
+
this.signal?.throwIfAborted();
|
|
667
|
+
|
|
623
668
|
if (typeof iterator !== 'function') {
|
|
624
669
|
throw new MongoInvalidArgumentError('Argument "iterator" must be a function');
|
|
625
670
|
}
|
|
@@ -645,6 +690,8 @@ export abstract class AbstractCursor<
|
|
|
645
690
|
* cursor.rewind() can be used to reset the cursor.
|
|
646
691
|
*/
|
|
647
692
|
async toArray(): Promise<TSchema[]> {
|
|
693
|
+
this.signal?.throwIfAborted();
|
|
694
|
+
|
|
648
695
|
const array: TSchema[] = [];
|
|
649
696
|
// at the end of the loop (since readBufferedDocuments is called) the buffer will be empty
|
|
650
697
|
// then, the 'await of' syntax will run a getMore call
|
|
@@ -824,16 +871,15 @@ export abstract class AbstractCursor<
|
|
|
824
871
|
this.isClosed = false;
|
|
825
872
|
this.isKilled = false;
|
|
826
873
|
this.initialized = false;
|
|
874
|
+
this.hasEmittedClose = false;
|
|
875
|
+
this.trackCursor();
|
|
827
876
|
|
|
828
|
-
|
|
829
|
-
if (
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (!session.hasEnded) {
|
|
833
|
-
session.endSession().then(undefined, squashError);
|
|
834
|
-
}
|
|
835
|
-
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
|
|
877
|
+
// We only want to end this session if we created it, and it hasn't ended yet
|
|
878
|
+
if (this.cursorSession.explicit === false) {
|
|
879
|
+
if (!this.cursorSession.hasEnded) {
|
|
880
|
+
this.cursorSession.endSession().then(undefined, squashError);
|
|
836
881
|
}
|
|
882
|
+
this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
|
|
837
883
|
}
|
|
838
884
|
}
|
|
839
885
|
|
|
@@ -968,8 +1014,8 @@ export abstract class AbstractCursor<
|
|
|
968
1014
|
|
|
969
1015
|
/** @internal */
|
|
970
1016
|
private async cleanup(timeoutMS?: number, error?: Error) {
|
|
1017
|
+
this.abortListener?.[kDispose]();
|
|
971
1018
|
this.isClosed = true;
|
|
972
|
-
const session = this.cursorSession;
|
|
973
1019
|
const timeoutContextForKillCursors = (): CursorTimeoutContext | undefined => {
|
|
974
1020
|
if (timeoutMS != null) {
|
|
975
1021
|
this.timeoutContext?.clear();
|
|
@@ -991,7 +1037,7 @@ export abstract class AbstractCursor<
|
|
|
991
1037
|
!this.cursorId.isZero() &&
|
|
992
1038
|
this.cursorNamespace &&
|
|
993
1039
|
this.selectedServer &&
|
|
994
|
-
!
|
|
1040
|
+
!this.cursorSession.hasEnded
|
|
995
1041
|
) {
|
|
996
1042
|
this.isKilled = true;
|
|
997
1043
|
const cursorId = this.cursorId;
|
|
@@ -1000,7 +1046,7 @@ export abstract class AbstractCursor<
|
|
|
1000
1046
|
await executeOperation(
|
|
1001
1047
|
this.cursorClient,
|
|
1002
1048
|
new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
|
|
1003
|
-
session
|
|
1049
|
+
session: this.cursorSession
|
|
1004
1050
|
}),
|
|
1005
1051
|
timeoutContextForKillCursors()
|
|
1006
1052
|
);
|
|
@@ -1008,14 +1054,16 @@ export abstract class AbstractCursor<
|
|
|
1008
1054
|
} catch (error) {
|
|
1009
1055
|
squashError(error);
|
|
1010
1056
|
} finally {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1057
|
+
try {
|
|
1058
|
+
if (this.cursorSession?.owner === this) {
|
|
1059
|
+
await this.cursorSession.endSession({ error });
|
|
1060
|
+
}
|
|
1061
|
+
if (!this.cursorSession?.inTransaction()) {
|
|
1062
|
+
maybeClearPinnedConnection(this.cursorSession, { error });
|
|
1063
|
+
}
|
|
1064
|
+
} finally {
|
|
1065
|
+
this.emitClose();
|
|
1016
1066
|
}
|
|
1017
|
-
|
|
1018
|
-
this.emitClose();
|
|
1019
1067
|
}
|
|
1020
1068
|
}
|
|
1021
1069
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
validateExplainTimeoutOptions
|
|
9
9
|
} from '../explain';
|
|
10
10
|
import type { MongoClient } from '../mongo_client';
|
|
11
|
+
import { type Abortable } from '../mongo_types';
|
|
11
12
|
import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
|
|
12
13
|
import { executeOperation } from '../operations/execute_operation';
|
|
13
14
|
import type { ClientSession } from '../sessions';
|
|
@@ -32,14 +33,14 @@ export interface AggregationCursorOptions extends AbstractCursorOptions, Aggrega
|
|
|
32
33
|
export class AggregationCursor<TSchema = any> extends ExplainableCursor<TSchema> {
|
|
33
34
|
public readonly pipeline: Document[];
|
|
34
35
|
/** @internal */
|
|
35
|
-
private aggregateOptions: AggregateOptions;
|
|
36
|
+
private aggregateOptions: AggregateOptions & Abortable;
|
|
36
37
|
|
|
37
38
|
/** @internal */
|
|
38
39
|
constructor(
|
|
39
40
|
client: MongoClient,
|
|
40
41
|
namespace: MongoDBNamespace,
|
|
41
42
|
pipeline: Document[] = [],
|
|
42
|
-
options: AggregateOptions = {}
|
|
43
|
+
options: AggregateOptions & Abortable = {}
|
|
43
44
|
) {
|
|
44
45
|
super(client, namespace, options);
|
|
45
46
|
|
|
@@ -73,7 +74,8 @@ export class AggregationCursor<TSchema = any> extends ExplainableCursor<TSchema>
|
|
|
73
74
|
const options = {
|
|
74
75
|
...this.aggregateOptions,
|
|
75
76
|
...this.cursorOptions,
|
|
76
|
-
session
|
|
77
|
+
session,
|
|
78
|
+
signal: this.signal
|
|
77
79
|
};
|
|
78
80
|
if (options.explain) {
|
|
79
81
|
try {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
validateExplainTimeoutOptions
|
|
10
10
|
} from '../explain';
|
|
11
11
|
import type { MongoClient } from '../mongo_client';
|
|
12
|
+
import { type Abortable } from '../mongo_types';
|
|
12
13
|
import type { CollationOptions } from '../operations/command';
|
|
13
14
|
import { CountOperation, type CountOptions } from '../operations/count';
|
|
14
15
|
import { executeOperation } from '../operations/execute_operation';
|
|
@@ -36,14 +37,14 @@ export class FindCursor<TSchema = any> extends ExplainableCursor<TSchema> {
|
|
|
36
37
|
/** @internal */
|
|
37
38
|
private numReturned = 0;
|
|
38
39
|
/** @internal */
|
|
39
|
-
private readonly findOptions: FindOptions;
|
|
40
|
+
private readonly findOptions: FindOptions & Abortable;
|
|
40
41
|
|
|
41
42
|
/** @internal */
|
|
42
43
|
constructor(
|
|
43
44
|
client: MongoClient,
|
|
44
45
|
namespace: MongoDBNamespace,
|
|
45
46
|
filter: Document = {},
|
|
46
|
-
options: FindOptions = {}
|
|
47
|
+
options: FindOptions & Abortable = {}
|
|
47
48
|
) {
|
|
48
49
|
super(client, namespace, options);
|
|
49
50
|
|
|
@@ -72,7 +73,8 @@ export class FindCursor<TSchema = any> extends ExplainableCursor<TSchema> {
|
|
|
72
73
|
const options = {
|
|
73
74
|
...this.findOptions, // NOTE: order matters here, we may need to refine this
|
|
74
75
|
...this.cursorOptions,
|
|
75
|
-
session
|
|
76
|
+
session,
|
|
77
|
+
signal: this.signal
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
if (options.explain) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Document } from '../bson';
|
|
2
2
|
import type { Db } from '../db';
|
|
3
|
+
import { type Abortable } from '../mongo_types';
|
|
3
4
|
import { executeOperation } from '../operations/execute_operation';
|
|
4
5
|
import {
|
|
5
6
|
type CollectionInfo,
|
|
@@ -17,9 +18,9 @@ export class ListCollectionsCursor<
|
|
|
17
18
|
> extends AbstractCursor<T> {
|
|
18
19
|
parent: Db;
|
|
19
20
|
filter: Document;
|
|
20
|
-
options?: ListCollectionsOptions;
|
|
21
|
+
options?: ListCollectionsOptions & Abortable;
|
|
21
22
|
|
|
22
|
-
constructor(db: Db, filter: Document, options?: ListCollectionsOptions) {
|
|
23
|
+
constructor(db: Db, filter: Document, options?: ListCollectionsOptions & Abortable) {
|
|
23
24
|
super(db.client, db.s.namespace, options);
|
|
24
25
|
this.parent = db;
|
|
25
26
|
this.filter = filter;
|
|
@@ -38,7 +39,8 @@ export class ListCollectionsCursor<
|
|
|
38
39
|
const operation = new ListCollectionsOperation(this.parent, this.filter, {
|
|
39
40
|
...this.cursorOptions,
|
|
40
41
|
...this.options,
|
|
41
|
-
session
|
|
42
|
+
session,
|
|
43
|
+
signal: this.signal
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
const response = await executeOperation(this.parent.client, operation, this.timeoutContext);
|
package/src/db.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { ListCollectionsCursor } from './cursor/list_collections_cursor';
|
|
|
8
8
|
import { RunCommandCursor, type RunCursorCommandOptions } from './cursor/run_command_cursor';
|
|
9
9
|
import { MongoInvalidArgumentError } from './error';
|
|
10
10
|
import type { MongoClient, PkFactory } from './mongo_client';
|
|
11
|
-
import type { TODO_NODE_3286 } from './mongo_types';
|
|
11
|
+
import type { Abortable, TODO_NODE_3286 } from './mongo_types';
|
|
12
12
|
import type { AggregateOptions } from './operations/aggregate';
|
|
13
13
|
import { CollectionsOperation } from './operations/collections';
|
|
14
14
|
import {
|
|
@@ -273,7 +273,7 @@ export class Db {
|
|
|
273
273
|
* @param command - The command to run
|
|
274
274
|
* @param options - Optional settings for the command
|
|
275
275
|
*/
|
|
276
|
-
async command(command: Document, options?: RunCommandOptions): Promise<Document> {
|
|
276
|
+
async command(command: Document, options?: RunCommandOptions & Abortable): Promise<Document> {
|
|
277
277
|
// Intentionally, we do not inherit options from parent for this operation.
|
|
278
278
|
return await executeOperation(
|
|
279
279
|
this.client,
|
|
@@ -284,7 +284,8 @@ export class Db {
|
|
|
284
284
|
...resolveBSONOptions(options),
|
|
285
285
|
timeoutMS: options?.timeoutMS ?? this.timeoutMS,
|
|
286
286
|
session: options?.session,
|
|
287
|
-
readPreference: options?.readPreference
|
|
287
|
+
readPreference: options?.readPreference,
|
|
288
|
+
signal: options?.signal
|
|
288
289
|
})
|
|
289
290
|
)
|
|
290
291
|
);
|
|
@@ -351,22 +352,25 @@ export class Db {
|
|
|
351
352
|
*/
|
|
352
353
|
listCollections(
|
|
353
354
|
filter: Document,
|
|
354
|
-
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true }
|
|
355
|
+
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true } & Abortable
|
|
355
356
|
): ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>;
|
|
356
357
|
listCollections(
|
|
357
358
|
filter: Document,
|
|
358
|
-
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false }
|
|
359
|
+
options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false } & Abortable
|
|
359
360
|
): ListCollectionsCursor<CollectionInfo>;
|
|
360
361
|
listCollections<
|
|
361
362
|
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
|
|
362
363
|
| Pick<CollectionInfo, 'name' | 'type'>
|
|
363
364
|
| CollectionInfo
|
|
364
|
-
>(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor<T>;
|
|
365
|
+
>(filter?: Document, options?: ListCollectionsOptions & Abortable): ListCollectionsCursor<T>;
|
|
365
366
|
listCollections<
|
|
366
367
|
T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
|
|
367
368
|
| Pick<CollectionInfo, 'name' | 'type'>
|
|
368
369
|
| CollectionInfo
|
|
369
|
-
>(
|
|
370
|
+
>(
|
|
371
|
+
filter: Document = {},
|
|
372
|
+
options: ListCollectionsOptions & Abortable = {}
|
|
373
|
+
): ListCollectionsCursor<T> {
|
|
370
374
|
return new ListCollectionsCursor<T>(this, filter, resolveOptions(this, options));
|
|
371
375
|
}
|
|
372
376
|
|
package/src/index.ts
CHANGED
package/src/mongo_client.ts
CHANGED
|
@@ -18,6 +18,7 @@ import type { ClientMetadata } from './cmap/handshake/client_metadata';
|
|
|
18
18
|
import type { CompressorName } from './cmap/wire_protocol/compression';
|
|
19
19
|
import { parseOptions, resolveSRVRecord } from './connection_string';
|
|
20
20
|
import { MONGO_CLIENT_EVENTS } from './constants';
|
|
21
|
+
import { type AbstractCursor } from './cursor/abstract_cursor';
|
|
21
22
|
import { Db, type DbOptions } from './db';
|
|
22
23
|
import type { Encrypter } from './encrypter';
|
|
23
24
|
import { MongoInvalidArgumentError } from './error';
|
|
@@ -323,6 +324,12 @@ export interface MongoClientPrivate {
|
|
|
323
324
|
* - used to notify the leak checker in our tests if test author forgot to clean up explicit sessions
|
|
324
325
|
*/
|
|
325
326
|
readonly activeSessions: Set<ClientSession>;
|
|
327
|
+
/**
|
|
328
|
+
* We keep a reference to the cursors that are created from this client.
|
|
329
|
+
* - used to track and close all cursors in client.close().
|
|
330
|
+
* Cursors in this set are ones that still need to have their close method invoked (no other conditions are considered)
|
|
331
|
+
*/
|
|
332
|
+
readonly activeCursors: Set<AbstractCursor>;
|
|
326
333
|
readonly sessionPool: ServerSessionPool;
|
|
327
334
|
readonly options: MongoOptions;
|
|
328
335
|
readonly readConcern?: ReadConcern;
|
|
@@ -398,6 +405,7 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
|
|
|
398
405
|
hasBeenClosed: false,
|
|
399
406
|
sessionPool: new ServerSessionPool(this),
|
|
400
407
|
activeSessions: new Set(),
|
|
408
|
+
activeCursors: new Set(),
|
|
401
409
|
authProviders: new MongoClientAuthProviders(),
|
|
402
410
|
|
|
403
411
|
get options() {
|
|
@@ -650,6 +658,11 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
|
|
|
650
658
|
writable: false
|
|
651
659
|
});
|
|
652
660
|
|
|
661
|
+
const activeCursorCloses = Array.from(this.s.activeCursors, cursor => cursor.close());
|
|
662
|
+
this.s.activeCursors.clear();
|
|
663
|
+
|
|
664
|
+
await Promise.all(activeCursorCloses);
|
|
665
|
+
|
|
653
666
|
const activeSessionEnds = Array.from(this.s.activeSessions, session => session.endSession());
|
|
654
667
|
this.s.activeSessions.clear();
|
|
655
668
|
|
package/src/mongo_types.ts
CHANGED
|
@@ -474,6 +474,44 @@ export class TypedEventEmitter<Events extends EventsDescription> extends EventEm
|
|
|
474
474
|
/** @public */
|
|
475
475
|
export class CancellationToken extends TypedEventEmitter<{ cancel(): void }> {}
|
|
476
476
|
|
|
477
|
+
/** @public */
|
|
478
|
+
export type Abortable = {
|
|
479
|
+
/**
|
|
480
|
+
* @experimental
|
|
481
|
+
* When provided, the corresponding `AbortController` can be used to abort an asynchronous action.
|
|
482
|
+
*
|
|
483
|
+
* The `signal.reason` value is used as the error thrown.
|
|
484
|
+
*
|
|
485
|
+
* @remarks
|
|
486
|
+
* **NOTE:** If an abort signal aborts an operation while the driver is writing to the underlying
|
|
487
|
+
* socket or reading the response from the server, the socket will be closed.
|
|
488
|
+
* If signals are aborted at a high rate during socket read/writes this can lead to a high rate of connection reestablishment.
|
|
489
|
+
*
|
|
490
|
+
* We plan to mitigate this in a future release, please follow NODE-6062 (`timeoutMS` expiration suffers the same limitation).
|
|
491
|
+
*
|
|
492
|
+
* AbortSignals are likely a best fit for human interactive interruption (ex. ctrl-C) where the frequency
|
|
493
|
+
* of cancellation is reasonably low. If a signal is programmatically aborted for 100s of operations you can empty
|
|
494
|
+
* the driver's connection pool.
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```js
|
|
498
|
+
* const controller = new AbortController();
|
|
499
|
+
* const { signal } = controller;
|
|
500
|
+
* process.on('SIGINT', () => controller.abort(new Error('^C pressed')));
|
|
501
|
+
*
|
|
502
|
+
* try {
|
|
503
|
+
* const res = await fetch('...', { signal });
|
|
504
|
+
* await collection.findOne(await res.json(), { signal });
|
|
505
|
+
* catch (error) {
|
|
506
|
+
* if (error === signal.reason) {
|
|
507
|
+
* // signal abort error handling
|
|
508
|
+
* }
|
|
509
|
+
* }
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
signal?: AbortSignal | undefined;
|
|
513
|
+
};
|
|
514
|
+
|
|
477
515
|
/**
|
|
478
516
|
* Helper types for dot-notation filter attributes
|
|
479
517
|
*/
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import type { Topology } from '../sdam/topology';
|
|
26
26
|
import type { ClientSession } from '../sessions';
|
|
27
27
|
import { TimeoutContext } from '../timeout';
|
|
28
|
-
import { supportsRetryableWrites } from '../utils';
|
|
28
|
+
import { abortable, supportsRetryableWrites } from '../utils';
|
|
29
29
|
import { AbstractOperation, Aspect } from './operation';
|
|
30
30
|
|
|
31
31
|
const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
|
|
@@ -64,7 +64,10 @@ export async function executeOperation<
|
|
|
64
64
|
throw new MongoRuntimeError('This method requires a valid operation instance');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const topology =
|
|
67
|
+
const topology =
|
|
68
|
+
client.topology == null
|
|
69
|
+
? await abortable(autoConnect(client), operation.options)
|
|
70
|
+
: client.topology;
|
|
68
71
|
|
|
69
72
|
// The driver sessions spec mandates that we implicitly create sessions for operations
|
|
70
73
|
// that are not explicitly provided with a session.
|
|
@@ -198,7 +201,8 @@ async function tryOperation<
|
|
|
198
201
|
let server = await topology.selectServer(selector, {
|
|
199
202
|
session,
|
|
200
203
|
operationName: operation.commandName,
|
|
201
|
-
timeoutContext
|
|
204
|
+
timeoutContext,
|
|
205
|
+
signal: operation.options.signal
|
|
202
206
|
});
|
|
203
207
|
|
|
204
208
|
const hasReadAspect = operation.hasAspect(Aspect.READ_OPERATION);
|
|
@@ -260,7 +264,8 @@ async function tryOperation<
|
|
|
260
264
|
server = await topology.selectServer(selector, {
|
|
261
265
|
session,
|
|
262
266
|
operationName: operation.commandName,
|
|
263
|
-
previousServer
|
|
267
|
+
previousServer,
|
|
268
|
+
signal: operation.options.signal
|
|
264
269
|
});
|
|
265
270
|
|
|
266
271
|
if (hasWriteAspect && !supportsRetryableWrites(server)) {
|
|
@@ -2,6 +2,7 @@ import type { Binary, Document } from '../bson';
|
|
|
2
2
|
import { CursorResponse } from '../cmap/wire_protocol/responses';
|
|
3
3
|
import { type CursorTimeoutContext, type CursorTimeoutMode } from '../cursor/abstract_cursor';
|
|
4
4
|
import type { Db } from '../db';
|
|
5
|
+
import { type Abortable } from '../mongo_types';
|
|
5
6
|
import type { Server } from '../sdam/server';
|
|
6
7
|
import type { ClientSession } from '../sessions';
|
|
7
8
|
import { type TimeoutContext } from '../timeout';
|
|
@@ -10,7 +11,9 @@ import { CommandOperation, type CommandOperationOptions } from './command';
|
|
|
10
11
|
import { Aspect, defineAspects } from './operation';
|
|
11
12
|
|
|
12
13
|
/** @public */
|
|
13
|
-
export interface ListCollectionsOptions
|
|
14
|
+
export interface ListCollectionsOptions
|
|
15
|
+
extends Omit<CommandOperationOptions, 'writeConcern'>,
|
|
16
|
+
Abortable {
|
|
14
17
|
/** Since 4.0: If true, will only return the collection name in the response, and will omit additional info */
|
|
15
18
|
nameOnly?: boolean;
|
|
16
19
|
/** Since 4.0: If true and nameOnly is true, allows a user without the required privilege (i.e. listCollections action on the database) to run the command when access control is enforced. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BSONSerializeOptions, type Document, resolveBSONOptions } from '../bson';
|
|
2
|
+
import { type Abortable } from '../mongo_types';
|
|
2
3
|
import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
|
|
3
4
|
import type { Server } from '../sdam/server';
|
|
4
5
|
import type { ClientSession } from '../sessions';
|
|
@@ -59,7 +60,7 @@ export abstract class AbstractOperation<TResult = any> {
|
|
|
59
60
|
// BSON serialization options
|
|
60
61
|
bsonOptions?: BSONSerializeOptions;
|
|
61
62
|
|
|
62
|
-
options: OperationOptions;
|
|
63
|
+
options: OperationOptions & Abortable;
|
|
63
64
|
|
|
64
65
|
/** Specifies the time an operation will run until it throws a timeout error. */
|
|
65
66
|
timeoutMS?: number;
|
|
@@ -68,7 +69,7 @@ export abstract class AbstractOperation<TResult = any> {
|
|
|
68
69
|
|
|
69
70
|
static aspects?: Set<symbol>;
|
|
70
71
|
|
|
71
|
-
constructor(options: OperationOptions = {}) {
|
|
72
|
+
constructor(options: OperationOptions & Abortable = {}) {
|
|
72
73
|
this.readPreference = this.hasAspect(Aspect.WRITE_OPERATION)
|
|
73
74
|
? ReadPreference.primary
|
|
74
75
|
: (ReadPreference.fromOptions(options) ?? ReadPreference.primary);
|