mongodb 6.12.0-dev.20250124.sha.70d476aa → 6.12.0-dev.20250128.sha.654069fc
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 +63 -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/connection_string.js +20 -2
- package/lib/connection_string.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 +12 -0
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_logger.js +96 -1
- package/lib/mongo_logger.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 +63 -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/connection_string.ts +24 -4
- 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 +21 -0
- package/src/mongo_logger.ts +128 -2
- 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() {
|
|
@@ -521,6 +529,10 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
|
|
|
521
529
|
* This means the time to setup the `MongoClient` does not count against `timeoutMS`.
|
|
522
530
|
* If you are using `timeoutMS` we recommend connecting your client explicitly in advance of any operation to avoid this inconsistent execution time.
|
|
523
531
|
*
|
|
532
|
+
* @remarks
|
|
533
|
+
* The driver will look up corresponding SRV and TXT records if the connection string starts with `mongodb+srv://`.
|
|
534
|
+
* If those look ups throw a DNS Timeout error, the driver will retry the look up once.
|
|
535
|
+
*
|
|
524
536
|
* @see docs.mongodb.org/manual/reference/connection-string/
|
|
525
537
|
*/
|
|
526
538
|
async connect(): Promise<this> {
|
|
@@ -646,6 +658,11 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
|
|
|
646
658
|
writable: false
|
|
647
659
|
});
|
|
648
660
|
|
|
661
|
+
const activeCursorCloses = Array.from(this.s.activeCursors, cursor => cursor.close());
|
|
662
|
+
this.s.activeCursors.clear();
|
|
663
|
+
|
|
664
|
+
await Promise.all(activeCursorCloses);
|
|
665
|
+
|
|
649
666
|
const activeSessionEnds = Array.from(this.s.activeSessions, session => session.endSession());
|
|
650
667
|
this.s.activeSessions.clear();
|
|
651
668
|
|
|
@@ -727,6 +744,10 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
|
|
|
727
744
|
* @remarks
|
|
728
745
|
* The programmatically provided options take precedence over the URI options.
|
|
729
746
|
*
|
|
747
|
+
* @remarks
|
|
748
|
+
* The driver will look up corresponding SRV and TXT records if the connection string starts with `mongodb+srv://`.
|
|
749
|
+
* If those look ups throw a DNS Timeout error, the driver will retry the look up once.
|
|
750
|
+
*
|
|
730
751
|
* @see https://www.mongodb.com/docs/manual/reference/connection-string/
|
|
731
752
|
*/
|
|
732
753
|
static async connect(url: string, options?: MongoClientOptions): Promise<MongoClient> {
|
package/src/mongo_logger.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { inspect, promisify } from 'util';
|
|
2
|
+
import { isUint8Array } from 'util/types';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type Binary,
|
|
6
|
+
type BSONRegExp,
|
|
7
|
+
type BSONSymbol,
|
|
8
|
+
type Code,
|
|
9
|
+
type DBRef,
|
|
10
|
+
type Decimal128,
|
|
11
|
+
type Document,
|
|
12
|
+
type Double,
|
|
13
|
+
EJSON,
|
|
14
|
+
type EJSONOptions,
|
|
15
|
+
type Int32,
|
|
16
|
+
type Long,
|
|
17
|
+
type MaxKey,
|
|
18
|
+
type MinKey,
|
|
19
|
+
type ObjectId,
|
|
20
|
+
type Timestamp
|
|
21
|
+
} from './bson';
|
|
4
22
|
import type { CommandStartedEvent } from './cmap/command_monitoring_events';
|
|
5
23
|
import type {
|
|
6
24
|
ConnectionCheckedInEvent,
|
|
@@ -413,6 +431,20 @@ export interface LogConvertible extends Record<string, any> {
|
|
|
413
431
|
toLog(): Record<string, any>;
|
|
414
432
|
}
|
|
415
433
|
|
|
434
|
+
type BSONObject =
|
|
435
|
+
| BSONRegExp
|
|
436
|
+
| BSONSymbol
|
|
437
|
+
| Code
|
|
438
|
+
| DBRef
|
|
439
|
+
| Decimal128
|
|
440
|
+
| Double
|
|
441
|
+
| Int32
|
|
442
|
+
| Long
|
|
443
|
+
| MaxKey
|
|
444
|
+
| MinKey
|
|
445
|
+
| ObjectId
|
|
446
|
+
| Timestamp
|
|
447
|
+
| Binary;
|
|
416
448
|
/** @internal */
|
|
417
449
|
export function stringifyWithMaxLen(
|
|
418
450
|
value: any,
|
|
@@ -421,13 +453,107 @@ export function stringifyWithMaxLen(
|
|
|
421
453
|
): string {
|
|
422
454
|
let strToTruncate = '';
|
|
423
455
|
|
|
456
|
+
let currentLength = 0;
|
|
457
|
+
const maxDocumentLengthEnsurer = function maxDocumentLengthEnsurer(key: string, value: any) {
|
|
458
|
+
if (currentLength >= maxDocumentLength) {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
// Account for root document
|
|
462
|
+
if (key === '') {
|
|
463
|
+
// Account for starting brace
|
|
464
|
+
currentLength += 1;
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// +4 accounts for 2 quotation marks, colon and comma after value
|
|
469
|
+
// Note that this potentially undercounts since it does not account for escape sequences which
|
|
470
|
+
// will have an additional backslash added to them once passed through JSON.stringify.
|
|
471
|
+
currentLength += key.length + 4;
|
|
472
|
+
|
|
473
|
+
if (value == null) return value;
|
|
474
|
+
|
|
475
|
+
switch (typeof value) {
|
|
476
|
+
case 'string':
|
|
477
|
+
// +2 accounts for quotes
|
|
478
|
+
// Note that this potentially undercounts similarly to the key length calculation
|
|
479
|
+
currentLength += value.length + 2;
|
|
480
|
+
break;
|
|
481
|
+
case 'number':
|
|
482
|
+
case 'bigint':
|
|
483
|
+
currentLength += String(value).length;
|
|
484
|
+
break;
|
|
485
|
+
case 'boolean':
|
|
486
|
+
currentLength += value ? 4 : 5;
|
|
487
|
+
break;
|
|
488
|
+
case 'object':
|
|
489
|
+
if (isUint8Array(value)) {
|
|
490
|
+
// '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
|
|
491
|
+
// This is an estimate based on the fact that the base64 is approximately 1.33x the length of
|
|
492
|
+
// the actual binary sequence https://en.wikipedia.org/wiki/Base64
|
|
493
|
+
currentLength += (22 + value.byteLength + value.byteLength * 0.33 + 18) | 0;
|
|
494
|
+
} else if ('_bsontype' in value) {
|
|
495
|
+
const v = value as BSONObject;
|
|
496
|
+
switch (v._bsontype) {
|
|
497
|
+
case 'Int32':
|
|
498
|
+
currentLength += String(v.value).length;
|
|
499
|
+
break;
|
|
500
|
+
case 'Double':
|
|
501
|
+
// Account for representing integers as <value>.0
|
|
502
|
+
currentLength +=
|
|
503
|
+
(v.value | 0) === v.value ? String(v.value).length + 2 : String(v.value).length;
|
|
504
|
+
break;
|
|
505
|
+
case 'Long':
|
|
506
|
+
currentLength += v.toString().length;
|
|
507
|
+
break;
|
|
508
|
+
case 'ObjectId':
|
|
509
|
+
// '{"$oid":"XXXXXXXXXXXXXXXXXXXXXXXX"}'
|
|
510
|
+
currentLength += 35;
|
|
511
|
+
break;
|
|
512
|
+
case 'MaxKey':
|
|
513
|
+
case 'MinKey':
|
|
514
|
+
// '{"$maxKey":1}' or '{"$minKey":1}'
|
|
515
|
+
currentLength += 13;
|
|
516
|
+
break;
|
|
517
|
+
case 'Binary':
|
|
518
|
+
// '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
|
|
519
|
+
// This is an estimate based on the fact that the base64 is approximately 1.33x the length of
|
|
520
|
+
// the actual binary sequence https://en.wikipedia.org/wiki/Base64
|
|
521
|
+
currentLength += (22 + value.position + value.position * 0.33 + 18) | 0;
|
|
522
|
+
break;
|
|
523
|
+
case 'Timestamp':
|
|
524
|
+
// '{"$timestamp":{"t":<t>,"i":<i>}}'
|
|
525
|
+
currentLength += 19 + String(v.t).length + 5 + String(v.i).length + 2;
|
|
526
|
+
break;
|
|
527
|
+
case 'Code':
|
|
528
|
+
// '{"$code":"<code>"}' or '{"$code":"<code>","$scope":<scope>}'
|
|
529
|
+
if (v.scope == null) {
|
|
530
|
+
currentLength += v.code.length + 10 + 2;
|
|
531
|
+
} else {
|
|
532
|
+
// Ignoring actual scope object, so this undercounts by a significant amount
|
|
533
|
+
currentLength += v.code.length + 10 + 11;
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
case 'BSONRegExp':
|
|
537
|
+
// '{"$regularExpression":{"pattern":"<pattern>","options":"<options>"}}'
|
|
538
|
+
currentLength += 34 + v.pattern.length + 13 + v.options.length + 3;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return value;
|
|
544
|
+
};
|
|
545
|
+
|
|
424
546
|
if (typeof value === 'string') {
|
|
425
547
|
strToTruncate = value;
|
|
426
548
|
} else if (typeof value === 'function') {
|
|
427
549
|
strToTruncate = value.name;
|
|
428
550
|
} else {
|
|
429
551
|
try {
|
|
430
|
-
|
|
552
|
+
if (maxDocumentLength !== 0) {
|
|
553
|
+
strToTruncate = EJSON.stringify(value, maxDocumentLengthEnsurer, 0, options);
|
|
554
|
+
} else {
|
|
555
|
+
strToTruncate = EJSON.stringify(value, options);
|
|
556
|
+
}
|
|
431
557
|
} catch (e) {
|
|
432
558
|
strToTruncate = `Extended JSON serialization failed with: ${e.message}`;
|
|
433
559
|
}
|
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
|
*/
|