mongodb 4.7.0 → 4.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/lib/change_stream.js +136 -271
- package/lib/change_stream.js.map +1 -1
- package/lib/cmap/command_monitoring_events.js +2 -3
- package/lib/cmap/command_monitoring_events.js.map +1 -1
- package/lib/cmap/connect.js +3 -6
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +2 -6
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/collection.js +18 -3
- package/lib/collection.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +55 -62
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/change_stream_cursor.js +115 -0
- package/lib/cursor/change_stream_cursor.js.map +1 -0
- package/lib/cursor/list_collections_cursor.js +37 -0
- package/lib/cursor/list_collections_cursor.js.map +1 -0
- package/lib/cursor/list_indexes_cursor.js +36 -0
- package/lib/cursor/list_indexes_cursor.js.map +1 -0
- package/lib/db.js +2 -2
- package/lib/db.js.map +1 -1
- package/lib/deps.js.map +1 -1
- package/lib/encrypter.js +5 -12
- package/lib/encrypter.js.map +1 -1
- package/lib/index.js +9 -7
- package/lib/index.js.map +1 -1
- package/lib/mongo_client.js +49 -21
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_types.js.map +1 -1
- package/lib/operations/common_functions.js.map +1 -1
- package/lib/operations/estimated_document_count.js +5 -0
- package/lib/operations/estimated_document_count.js.map +1 -1
- package/lib/operations/execute_operation.js +17 -8
- package/lib/operations/execute_operation.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 +1 -32
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/kill_cursors.js +22 -0
- package/lib/operations/kill_cursors.js.map +1 -0
- package/lib/operations/list_collections.js +1 -33
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/operation.js +4 -1
- package/lib/operations/operation.js.map +1 -1
- package/lib/read_preference.js.map +1 -1
- package/lib/sdam/topology.js +25 -64
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +27 -30
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +25 -7
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +60 -27
- package/package.json +18 -18
- package/src/change_stream.ts +147 -373
- package/src/cmap/command_monitoring_events.ts +2 -3
- package/src/cmap/connect.ts +20 -25
- package/src/cmap/connection.ts +1 -2
- package/src/cmap/wire_protocol/compression.ts +8 -6
- package/src/collection.ts +21 -7
- package/src/cursor/abstract_cursor.ts +66 -71
- package/src/cursor/change_stream_cursor.ts +194 -0
- package/src/cursor/list_collections_cursor.ts +52 -0
- package/src/cursor/list_indexes_cursor.ts +41 -0
- package/src/db.ts +2 -5
- package/src/deps.ts +13 -22
- package/src/encrypter.ts +5 -13
- package/src/index.ts +9 -5
- package/src/mongo_client.ts +68 -27
- package/src/mongo_types.ts +29 -3
- package/src/operations/common_functions.ts +1 -1
- package/src/operations/estimated_document_count.ts +6 -0
- package/src/operations/execute_operation.ts +17 -8
- package/src/operations/get_more.ts +2 -2
- package/src/operations/indexes.ts +0 -37
- package/src/operations/kill_cursors.ts +27 -0
- package/src/operations/list_collections.ts +0 -43
- package/src/operations/operation.ts +5 -1
- package/src/read_preference.ts +5 -9
- package/src/sdam/topology.ts +35 -101
- package/src/sessions.ts +30 -38
- package/src/utils.ts +32 -8
package/src/cmap/connect.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type { Document } from '../bson';
|
|
|
8
8
|
import { Int32 } from '../bson';
|
|
9
9
|
import { LEGACY_HELLO_COMMAND } from '../constants';
|
|
10
10
|
import {
|
|
11
|
-
AnyError,
|
|
12
11
|
MongoCompatibilityError,
|
|
13
12
|
MongoError,
|
|
14
13
|
MongoErrorLabel,
|
|
@@ -462,40 +461,36 @@ function makeSocks5Connection(options: MakeConnectionOptions, callback: Callback
|
|
|
462
461
|
}
|
|
463
462
|
|
|
464
463
|
// Then, establish the Socks5 proxy connection:
|
|
465
|
-
SocksClient.createConnection(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
port: destination.port
|
|
473
|
-
},
|
|
474
|
-
proxy: {
|
|
475
|
-
// host and port are ignored because we pass existing_socket
|
|
476
|
-
host: 'iLoveJavaScript',
|
|
477
|
-
port: 0,
|
|
478
|
-
type: 5,
|
|
479
|
-
userId: options.proxyUsername || undefined,
|
|
480
|
-
password: options.proxyPassword || undefined
|
|
481
|
-
}
|
|
464
|
+
SocksClient.createConnection({
|
|
465
|
+
existing_socket: rawSocket,
|
|
466
|
+
timeout: options.connectTimeoutMS,
|
|
467
|
+
command: 'connect',
|
|
468
|
+
destination: {
|
|
469
|
+
host: destination.host,
|
|
470
|
+
port: destination.port
|
|
482
471
|
},
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
472
|
+
proxy: {
|
|
473
|
+
// host and port are ignored because we pass existing_socket
|
|
474
|
+
host: 'iLoveJavaScript',
|
|
475
|
+
port: 0,
|
|
476
|
+
type: 5,
|
|
477
|
+
userId: options.proxyUsername || undefined,
|
|
478
|
+
password: options.proxyPassword || undefined
|
|
479
|
+
}
|
|
480
|
+
}).then(
|
|
481
|
+
({ socket }) => {
|
|
488
482
|
// Finally, now treat the resulting duplex stream as the
|
|
489
483
|
// socket over which we send and receive wire protocol messages:
|
|
490
484
|
makeConnection(
|
|
491
485
|
{
|
|
492
486
|
...options,
|
|
493
|
-
existingSocket:
|
|
487
|
+
existingSocket: socket,
|
|
494
488
|
proxyHost: undefined
|
|
495
489
|
},
|
|
496
490
|
callback
|
|
497
491
|
);
|
|
498
|
-
}
|
|
492
|
+
},
|
|
493
|
+
error => callback(connectionFailureError('error', error))
|
|
499
494
|
);
|
|
500
495
|
}
|
|
501
496
|
);
|
package/src/cmap/connection.ts
CHANGED
|
@@ -533,7 +533,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
533
533
|
clusterTime = session.clusterTime;
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
-
const err = applySession(session, finalCmd, options
|
|
536
|
+
const err = applySession(session, finalCmd, options);
|
|
537
537
|
if (err) {
|
|
538
538
|
return callback(err);
|
|
539
539
|
}
|
|
@@ -727,7 +727,6 @@ export class CryptoConnection extends Connection {
|
|
|
727
727
|
callback(err, null);
|
|
728
728
|
return;
|
|
729
729
|
}
|
|
730
|
-
|
|
731
730
|
super.command(ns, encrypted, options, (err, response) => {
|
|
732
731
|
if (err || response == null) {
|
|
733
732
|
callback(err, response);
|
|
@@ -52,9 +52,10 @@ export function compress(
|
|
|
52
52
|
if (Snappy[PKG_VERSION].major <= 6) {
|
|
53
53
|
Snappy.compress(dataToBeCompressed, callback);
|
|
54
54
|
} else {
|
|
55
|
-
Snappy.compress(dataToBeCompressed)
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
Snappy.compress(dataToBeCompressed).then(
|
|
56
|
+
buffer => callback(undefined, buffer),
|
|
57
|
+
error => callback(error)
|
|
58
|
+
);
|
|
58
59
|
}
|
|
59
60
|
break;
|
|
60
61
|
}
|
|
@@ -102,9 +103,10 @@ export function decompress(
|
|
|
102
103
|
if (Snappy[PKG_VERSION].major <= 6) {
|
|
103
104
|
Snappy.uncompress(compressedData, { asBuffer: true }, callback);
|
|
104
105
|
} else {
|
|
105
|
-
Snappy.uncompress(compressedData, { asBuffer: true })
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
Snappy.uncompress(compressedData, { asBuffer: true }).then(
|
|
107
|
+
buffer => callback(undefined, buffer),
|
|
108
|
+
error => callback(error)
|
|
109
|
+
);
|
|
108
110
|
}
|
|
109
111
|
break;
|
|
110
112
|
}
|
package/src/collection.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { UnorderedBulkOperation } from './bulk/unordered';
|
|
|
5
5
|
import { ChangeStream, ChangeStreamDocument, ChangeStreamOptions } from './change_stream';
|
|
6
6
|
import { AggregationCursor } from './cursor/aggregation_cursor';
|
|
7
7
|
import { FindCursor } from './cursor/find_cursor';
|
|
8
|
+
import { ListIndexesCursor } from './cursor/list_indexes_cursor';
|
|
8
9
|
import type { Db } from './db';
|
|
9
10
|
import { MongoInvalidArgumentError } from './error';
|
|
10
11
|
import type { Logger, LoggerOptions } from './logger';
|
|
@@ -57,7 +58,6 @@ import {
|
|
|
57
58
|
IndexExistsOperation,
|
|
58
59
|
IndexInformationOperation,
|
|
59
60
|
IndexSpecification,
|
|
60
|
-
ListIndexesCursor,
|
|
61
61
|
ListIndexesOptions
|
|
62
62
|
} from './operations/indexes';
|
|
63
63
|
import {
|
|
@@ -725,7 +725,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
725
725
|
}
|
|
726
726
|
|
|
727
727
|
if (typeof filter === 'function') {
|
|
728
|
-
callback = filter
|
|
728
|
+
callback = filter;
|
|
729
729
|
filter = {};
|
|
730
730
|
options = {};
|
|
731
731
|
}
|
|
@@ -1128,7 +1128,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
1128
1128
|
this.s.db.s.client,
|
|
1129
1129
|
new CountDocumentsOperation(
|
|
1130
1130
|
this as TODO_NODE_3286,
|
|
1131
|
-
filter
|
|
1131
|
+
filter,
|
|
1132
1132
|
resolveOptions(this, options as CountDocumentsOptions)
|
|
1133
1133
|
),
|
|
1134
1134
|
callback
|
|
@@ -1191,7 +1191,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
1191
1191
|
callback?: Callback<any[]>
|
|
1192
1192
|
): Promise<any[]> | void {
|
|
1193
1193
|
if (typeof filter === 'function') {
|
|
1194
|
-
(callback = filter
|
|
1194
|
+
(callback = filter), (filter = {}), (options = {});
|
|
1195
1195
|
} else {
|
|
1196
1196
|
if (arguments.length === 3 && typeof options === 'function') {
|
|
1197
1197
|
(callback = options), (options = {});
|
|
@@ -1544,12 +1544,26 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
1544
1544
|
);
|
|
1545
1545
|
}
|
|
1546
1546
|
|
|
1547
|
-
/**
|
|
1547
|
+
/**
|
|
1548
|
+
* Initiate an Out of order batch write operation. All operations will be buffered into insert/update/remove commands executed out of order.
|
|
1549
|
+
*
|
|
1550
|
+
* @throws MongoNotConnectedError
|
|
1551
|
+
* @remarks
|
|
1552
|
+
* **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implemenation.
|
|
1553
|
+
* However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.
|
|
1554
|
+
*/
|
|
1548
1555
|
initializeUnorderedBulkOp(options?: BulkWriteOptions): UnorderedBulkOperation {
|
|
1549
1556
|
return new UnorderedBulkOperation(this as TODO_NODE_3286, resolveOptions(this, options));
|
|
1550
1557
|
}
|
|
1551
1558
|
|
|
1552
|
-
/**
|
|
1559
|
+
/**
|
|
1560
|
+
* Initiate an In order bulk write operation. Operations will be serially executed in the order they are added, creating a new operation for each switch in types.
|
|
1561
|
+
*
|
|
1562
|
+
* @throws MongoNotConnectedError
|
|
1563
|
+
* @remarks
|
|
1564
|
+
* **NOTE:** MongoClient must be connected prior to calling this method due to a known limitation in this legacy implemenation.
|
|
1565
|
+
* However, `collection.bulkWrite()` provides an equivalent API that does not require prior connecting.
|
|
1566
|
+
*/
|
|
1553
1567
|
initializeOrderedBulkOp(options?: BulkWriteOptions): OrderedBulkOperation {
|
|
1554
1568
|
return new OrderedBulkOperation(this as TODO_NODE_3286, resolveOptions(this, options));
|
|
1555
1569
|
}
|
|
@@ -1667,7 +1681,7 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
1667
1681
|
callback?: Callback<number>
|
|
1668
1682
|
): Promise<number> | void {
|
|
1669
1683
|
if (typeof filter === 'function') {
|
|
1670
|
-
(callback = filter
|
|
1684
|
+
(callback = filter), (filter = {}), (options = {});
|
|
1671
1685
|
} else {
|
|
1672
1686
|
if (typeof options === 'function') (callback = options), (options = {});
|
|
1673
1687
|
}
|
|
@@ -14,6 +14,7 @@ import type { MongoClient } from '../mongo_client';
|
|
|
14
14
|
import { TODO_NODE_3286, TypedEventEmitter } from '../mongo_types';
|
|
15
15
|
import { executeOperation, ExecutionResult } from '../operations/execute_operation';
|
|
16
16
|
import { GetMoreOperation } from '../operations/get_more';
|
|
17
|
+
import { KillCursorsOperation } from '../operations/kill_cursors';
|
|
17
18
|
import { ReadConcern, ReadConcernLike } from '../read_concern';
|
|
18
19
|
import { ReadPreference, ReadPreferenceLike } from '../read_preference';
|
|
19
20
|
import type { Server } from '../sdam/server';
|
|
@@ -66,7 +67,7 @@ export interface CursorCloseOptions {
|
|
|
66
67
|
/** @public */
|
|
67
68
|
export interface CursorStreamOptions {
|
|
68
69
|
/** A transformation method applied to each document emitted by the stream */
|
|
69
|
-
transform?(doc: Document): Document;
|
|
70
|
+
transform?(this: void, doc: Document): Document;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/** @public */
|
|
@@ -118,7 +119,7 @@ export abstract class AbstractCursor<
|
|
|
118
119
|
/** @internal */
|
|
119
120
|
[kId]?: Long;
|
|
120
121
|
/** @internal */
|
|
121
|
-
[kSession]
|
|
122
|
+
[kSession]: ClientSession;
|
|
122
123
|
/** @internal */
|
|
123
124
|
[kServer]?: Server;
|
|
124
125
|
/** @internal */
|
|
@@ -187,6 +188,8 @@ export abstract class AbstractCursor<
|
|
|
187
188
|
|
|
188
189
|
if (options.session instanceof ClientSession) {
|
|
189
190
|
this[kSession] = options.session;
|
|
191
|
+
} else {
|
|
192
|
+
this[kSession] = this[kClient].startSession({ owner: this, explicit: false });
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
|
|
@@ -217,11 +220,11 @@ export abstract class AbstractCursor<
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
/** @internal */
|
|
220
|
-
get session(): ClientSession
|
|
223
|
+
get session(): ClientSession {
|
|
221
224
|
return this[kSession];
|
|
222
225
|
}
|
|
223
226
|
|
|
224
|
-
set session(clientSession: ClientSession
|
|
227
|
+
set session(clientSession: ClientSession) {
|
|
225
228
|
this[kSession] = clientSession;
|
|
226
229
|
}
|
|
227
230
|
|
|
@@ -264,7 +267,7 @@ export abstract class AbstractCursor<
|
|
|
264
267
|
stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
|
|
265
268
|
if (options?.transform) {
|
|
266
269
|
const transform = options.transform;
|
|
267
|
-
const readable =
|
|
270
|
+
const readable = new ReadableCursorStream(this);
|
|
268
271
|
|
|
269
272
|
return readable.pipe(
|
|
270
273
|
new Transform({
|
|
@@ -282,7 +285,7 @@ export abstract class AbstractCursor<
|
|
|
282
285
|
);
|
|
283
286
|
}
|
|
284
287
|
|
|
285
|
-
return
|
|
288
|
+
return new ReadableCursorStream(this);
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
hasNext(): Promise<boolean>;
|
|
@@ -592,11 +595,12 @@ export abstract class AbstractCursor<
|
|
|
592
595
|
const session = this[kSession];
|
|
593
596
|
if (session) {
|
|
594
597
|
// We only want to end this session if we created it, and it hasn't ended yet
|
|
595
|
-
if (session.explicit === false
|
|
596
|
-
session.
|
|
598
|
+
if (session.explicit === false) {
|
|
599
|
+
if (!session.hasEnded) {
|
|
600
|
+
session.endSession().catch(() => null);
|
|
601
|
+
}
|
|
602
|
+
this[kSession] = this.client.startSession({ owner: this, explicit: false });
|
|
597
603
|
}
|
|
598
|
-
|
|
599
|
-
this[kSession] = undefined;
|
|
600
604
|
}
|
|
601
605
|
}
|
|
602
606
|
|
|
@@ -644,22 +648,10 @@ export abstract class AbstractCursor<
|
|
|
644
648
|
* a significant refactor.
|
|
645
649
|
*/
|
|
646
650
|
[kInit](callback: Callback<TSchema | null>): void {
|
|
647
|
-
if (this[kSession] == null) {
|
|
648
|
-
if (this[kClient].topology?.shouldCheckForSessionSupport()) {
|
|
649
|
-
return this[kClient].topology?.selectServer(ReadPreference.primaryPreferred, {}, err => {
|
|
650
|
-
if (err) return callback(err);
|
|
651
|
-
return this[kInit](callback);
|
|
652
|
-
});
|
|
653
|
-
} else if (this[kClient].topology?.hasSessionSupport()) {
|
|
654
|
-
this[kSession] = this[kClient].topology?.startSession({ owner: this, explicit: false });
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
651
|
this._initialize(this[kSession], (err, state) => {
|
|
659
652
|
if (state) {
|
|
660
653
|
const response = state.response;
|
|
661
654
|
this[kServer] = state.server;
|
|
662
|
-
this[kSession] = state.session;
|
|
663
655
|
|
|
664
656
|
if (response.cursor) {
|
|
665
657
|
this[kId] =
|
|
@@ -714,7 +706,21 @@ function nextDocument<T>(cursor: AbstractCursor): T | null {
|
|
|
714
706
|
return null;
|
|
715
707
|
}
|
|
716
708
|
|
|
717
|
-
|
|
709
|
+
/**
|
|
710
|
+
* @param cursor - the cursor on which to call `next`
|
|
711
|
+
* @param blocking - a boolean indicating whether or not the cursor should `block` until data
|
|
712
|
+
* is available. Generally, this flag is set to `false` because if the getMore returns no documents,
|
|
713
|
+
* the cursor has been exhausted. In certain scenarios (ChangeStreams, tailable await cursors and
|
|
714
|
+
* `tryNext`, for example) blocking is necessary because a getMore returning no documents does
|
|
715
|
+
* not indicate the end of the cursor.
|
|
716
|
+
* @param callback - callback to return the result to the caller
|
|
717
|
+
* @returns
|
|
718
|
+
*/
|
|
719
|
+
export function next<T>(
|
|
720
|
+
cursor: AbstractCursor<T>,
|
|
721
|
+
blocking: boolean,
|
|
722
|
+
callback: Callback<T | null>
|
|
723
|
+
): void {
|
|
718
724
|
const cursorId = cursor[kId];
|
|
719
725
|
if (cursor.closed) {
|
|
720
726
|
return callback(undefined, null);
|
|
@@ -829,11 +835,11 @@ function cleanupCursor(
|
|
|
829
835
|
}
|
|
830
836
|
|
|
831
837
|
cursor[kKilled] = true;
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
[
|
|
835
|
-
|
|
836
|
-
|
|
838
|
+
|
|
839
|
+
return executeOperation(
|
|
840
|
+
cursor[kClient],
|
|
841
|
+
new KillCursorsOperation(cursorId, cursorNs, server, { session }),
|
|
842
|
+
completeCleanup
|
|
837
843
|
);
|
|
838
844
|
}
|
|
839
845
|
|
|
@@ -844,50 +850,41 @@ export function assertUninitialized(cursor: AbstractCursor): void {
|
|
|
844
850
|
}
|
|
845
851
|
}
|
|
846
852
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
autoDestroy: false,
|
|
851
|
-
highWaterMark: 1
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
let initialized = false;
|
|
855
|
-
let reading = false;
|
|
856
|
-
let needToClose = true; // NOTE: we must close the cursor if we never read from it, use `_construct` in future node versions
|
|
857
|
-
|
|
858
|
-
readable._read = function () {
|
|
859
|
-
if (initialized === false) {
|
|
860
|
-
needToClose = false;
|
|
861
|
-
initialized = true;
|
|
862
|
-
}
|
|
853
|
+
class ReadableCursorStream extends Readable {
|
|
854
|
+
private _cursor: AbstractCursor;
|
|
855
|
+
private _readInProgress = false;
|
|
863
856
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
857
|
+
constructor(cursor: AbstractCursor) {
|
|
858
|
+
super({
|
|
859
|
+
objectMode: true,
|
|
860
|
+
autoDestroy: false,
|
|
861
|
+
highWaterMark: 1
|
|
862
|
+
});
|
|
863
|
+
this._cursor = cursor;
|
|
864
|
+
}
|
|
869
865
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
866
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
867
|
+
override _read(size: number): void {
|
|
868
|
+
if (!this._readInProgress) {
|
|
869
|
+
this._readInProgress = true;
|
|
870
|
+
this._readNext();
|
|
875
871
|
}
|
|
876
|
-
}
|
|
872
|
+
}
|
|
877
873
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
needToClose = err ? !cursor.closed : result != null;
|
|
874
|
+
override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
|
|
875
|
+
this._cursor.close(err => process.nextTick(callback, err || error));
|
|
876
|
+
}
|
|
882
877
|
|
|
878
|
+
private _readNext() {
|
|
879
|
+
next(this._cursor, true, (err, result) => {
|
|
883
880
|
if (err) {
|
|
884
881
|
// NOTE: This is questionable, but we have a test backing the behavior. It seems the
|
|
885
882
|
// desired behavior is that a stream ends cleanly when a user explicitly closes
|
|
886
883
|
// a client during iteration. Alternatively, we could do the "right" thing and
|
|
887
884
|
// propagate the error message by removing this special case.
|
|
888
885
|
if (err.message.match(/server is closed/)) {
|
|
889
|
-
|
|
890
|
-
return
|
|
886
|
+
this._cursor.close().catch(() => null);
|
|
887
|
+
return this.push(null);
|
|
891
888
|
}
|
|
892
889
|
|
|
893
890
|
// NOTE: This is also perhaps questionable. The rationale here is that these errors tend
|
|
@@ -896,25 +893,23 @@ function makeCursorStream(cursor: AbstractCursor) {
|
|
|
896
893
|
// that changed to happen in cleanup legitimate errors would not destroy the
|
|
897
894
|
// stream. There are change streams test specifically test these cases.
|
|
898
895
|
if (err.message.match(/interrupted/)) {
|
|
899
|
-
return
|
|
896
|
+
return this.push(null);
|
|
900
897
|
}
|
|
901
898
|
|
|
902
|
-
return
|
|
899
|
+
return this.destroy(err);
|
|
903
900
|
}
|
|
904
901
|
|
|
905
902
|
if (result == null) {
|
|
906
|
-
|
|
907
|
-
} else if (
|
|
908
|
-
|
|
903
|
+
this.push(null);
|
|
904
|
+
} else if (this.destroyed) {
|
|
905
|
+
this._cursor.close().catch(() => null);
|
|
909
906
|
} else {
|
|
910
|
-
if (
|
|
911
|
-
return
|
|
907
|
+
if (this.push(result)) {
|
|
908
|
+
return this._readNext();
|
|
912
909
|
}
|
|
913
910
|
|
|
914
|
-
|
|
911
|
+
this._readInProgress = false;
|
|
915
912
|
}
|
|
916
913
|
});
|
|
917
914
|
}
|
|
918
|
-
|
|
919
|
-
return readable;
|
|
920
915
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { Document, Long, Timestamp } from '../bson';
|
|
2
|
+
import {
|
|
3
|
+
type ChangeStreamDocument,
|
|
4
|
+
type ChangeStreamEvents,
|
|
5
|
+
type OperationTime,
|
|
6
|
+
type ResumeToken,
|
|
7
|
+
ChangeStream
|
|
8
|
+
} from '../change_stream';
|
|
9
|
+
import { INIT, RESPONSE } from '../constants';
|
|
10
|
+
import type { MongoClient } from '../mongo_client';
|
|
11
|
+
import type { TODO_NODE_3286 } from '../mongo_types';
|
|
12
|
+
import { AggregateOperation } from '../operations/aggregate';
|
|
13
|
+
import type { CollationOptions } from '../operations/command';
|
|
14
|
+
import { type ExecutionResult, executeOperation } from '../operations/execute_operation';
|
|
15
|
+
import type { ClientSession } from '../sessions';
|
|
16
|
+
import { type Callback, type MongoDBNamespace, maxWireVersion } from '../utils';
|
|
17
|
+
import { type AbstractCursorOptions, AbstractCursor } from './abstract_cursor';
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
export interface ChangeStreamCursorOptions extends AbstractCursorOptions {
|
|
21
|
+
startAtOperationTime?: OperationTime;
|
|
22
|
+
resumeAfter?: ResumeToken;
|
|
23
|
+
startAfter?: ResumeToken;
|
|
24
|
+
maxAwaitTimeMS?: number;
|
|
25
|
+
collation?: CollationOptions;
|
|
26
|
+
fullDocument?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @internal */
|
|
30
|
+
export type ChangeStreamAggregateRawResult<TChange> = {
|
|
31
|
+
$clusterTime: { clusterTime: Timestamp };
|
|
32
|
+
cursor: {
|
|
33
|
+
postBatchResumeToken: ResumeToken;
|
|
34
|
+
ns: string;
|
|
35
|
+
id: number | Long;
|
|
36
|
+
} & ({ firstBatch: TChange[] } | { nextBatch: TChange[] });
|
|
37
|
+
ok: 1;
|
|
38
|
+
operationTime: Timestamp;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** @internal */
|
|
42
|
+
export class ChangeStreamCursor<
|
|
43
|
+
TSchema extends Document = Document,
|
|
44
|
+
TChange extends Document = ChangeStreamDocument<TSchema>
|
|
45
|
+
> extends AbstractCursor<TChange, ChangeStreamEvents> {
|
|
46
|
+
_resumeToken: ResumeToken;
|
|
47
|
+
startAtOperationTime?: OperationTime;
|
|
48
|
+
hasReceived?: boolean;
|
|
49
|
+
resumeAfter: ResumeToken;
|
|
50
|
+
startAfter: ResumeToken;
|
|
51
|
+
options: ChangeStreamCursorOptions;
|
|
52
|
+
|
|
53
|
+
postBatchResumeToken?: ResumeToken;
|
|
54
|
+
pipeline: Document[];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @internal
|
|
58
|
+
*
|
|
59
|
+
* used to determine change stream resumability
|
|
60
|
+
*/
|
|
61
|
+
maxWireVersion: number | undefined;
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
client: MongoClient,
|
|
65
|
+
namespace: MongoDBNamespace,
|
|
66
|
+
pipeline: Document[] = [],
|
|
67
|
+
options: ChangeStreamCursorOptions = {}
|
|
68
|
+
) {
|
|
69
|
+
super(client, namespace, options);
|
|
70
|
+
|
|
71
|
+
this.pipeline = pipeline;
|
|
72
|
+
this.options = options;
|
|
73
|
+
this._resumeToken = null;
|
|
74
|
+
this.startAtOperationTime = options.startAtOperationTime;
|
|
75
|
+
|
|
76
|
+
if (options.startAfter) {
|
|
77
|
+
this.resumeToken = options.startAfter;
|
|
78
|
+
} else if (options.resumeAfter) {
|
|
79
|
+
this.resumeToken = options.resumeAfter;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
set resumeToken(token: ResumeToken) {
|
|
84
|
+
this._resumeToken = token;
|
|
85
|
+
this.emit(ChangeStream.RESUME_TOKEN_CHANGED, token);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get resumeToken(): ResumeToken {
|
|
89
|
+
return this._resumeToken;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get resumeOptions(): ChangeStreamCursorOptions {
|
|
93
|
+
const options: ChangeStreamCursorOptions = {
|
|
94
|
+
...this.options
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
for (const key of ['resumeAfter', 'startAfter', 'startAtOperationTime'] as const) {
|
|
98
|
+
delete options[key];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (this.resumeToken != null) {
|
|
102
|
+
if (this.options.startAfter && !this.hasReceived) {
|
|
103
|
+
options.startAfter = this.resumeToken;
|
|
104
|
+
} else {
|
|
105
|
+
options.resumeAfter = this.resumeToken;
|
|
106
|
+
}
|
|
107
|
+
} else if (this.startAtOperationTime != null && maxWireVersion(this.server) >= 7) {
|
|
108
|
+
options.startAtOperationTime = this.startAtOperationTime;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return options;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
cacheResumeToken(resumeToken: ResumeToken): void {
|
|
115
|
+
if (this.bufferedCount() === 0 && this.postBatchResumeToken) {
|
|
116
|
+
this.resumeToken = this.postBatchResumeToken;
|
|
117
|
+
} else {
|
|
118
|
+
this.resumeToken = resumeToken;
|
|
119
|
+
}
|
|
120
|
+
this.hasReceived = true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_processBatch(response: ChangeStreamAggregateRawResult<TChange>): void {
|
|
124
|
+
const cursor = response.cursor;
|
|
125
|
+
if (cursor.postBatchResumeToken) {
|
|
126
|
+
this.postBatchResumeToken = response.cursor.postBatchResumeToken;
|
|
127
|
+
|
|
128
|
+
const batch =
|
|
129
|
+
'firstBatch' in response.cursor ? response.cursor.firstBatch : response.cursor.nextBatch;
|
|
130
|
+
if (batch.length === 0) {
|
|
131
|
+
this.resumeToken = cursor.postBatchResumeToken;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
clone(): AbstractCursor<TChange> {
|
|
137
|
+
return new ChangeStreamCursor(this.client, this.namespace, this.pipeline, {
|
|
138
|
+
...this.cursorOptions
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_initialize(session: ClientSession, callback: Callback<ExecutionResult>): void {
|
|
143
|
+
const aggregateOperation = new AggregateOperation(this.namespace, this.pipeline, {
|
|
144
|
+
...this.cursorOptions,
|
|
145
|
+
...this.options,
|
|
146
|
+
session
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
executeOperation<TODO_NODE_3286, ChangeStreamAggregateRawResult<TChange>>(
|
|
150
|
+
session.client,
|
|
151
|
+
aggregateOperation,
|
|
152
|
+
(err, response) => {
|
|
153
|
+
if (err || response == null) {
|
|
154
|
+
return callback(err);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const server = aggregateOperation.server;
|
|
158
|
+
this.maxWireVersion = maxWireVersion(server);
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
this.startAtOperationTime == null &&
|
|
162
|
+
this.resumeAfter == null &&
|
|
163
|
+
this.startAfter == null &&
|
|
164
|
+
this.maxWireVersion >= 7
|
|
165
|
+
) {
|
|
166
|
+
this.startAtOperationTime = response.operationTime;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this._processBatch(response);
|
|
170
|
+
|
|
171
|
+
this.emit(INIT, response);
|
|
172
|
+
this.emit(RESPONSE);
|
|
173
|
+
|
|
174
|
+
// TODO: NODE-2882
|
|
175
|
+
callback(undefined, { server, session, response });
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override _getMore(batchSize: number, callback: Callback): void {
|
|
181
|
+
super._getMore(batchSize, (err, response) => {
|
|
182
|
+
if (err) {
|
|
183
|
+
return callback(err);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.maxWireVersion = maxWireVersion(this.server);
|
|
187
|
+
this._processBatch(response as TODO_NODE_3286 as ChangeStreamAggregateRawResult<TChange>);
|
|
188
|
+
|
|
189
|
+
this.emit(ChangeStream.MORE, response);
|
|
190
|
+
this.emit(ChangeStream.RESPONSE);
|
|
191
|
+
callback(err, response);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|