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
package/src/cmap/connection.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { type Readable, Transform, type TransformCallback } from 'stream';
|
|
2
2
|
import { clearTimeout, setTimeout } from 'timers';
|
|
3
3
|
|
|
4
|
-
import type
|
|
5
|
-
import type
|
|
4
|
+
import { type BSONSerializeOptions, deserialize, type Document, type ObjectId } from '../bson';
|
|
5
|
+
import { type AutoEncrypter } from '../client-side-encryption/auto_encrypter';
|
|
6
6
|
import {
|
|
7
7
|
CLOSE,
|
|
8
8
|
CLUSTER_TIME_RECEIVED,
|
|
9
9
|
COMMAND_FAILED,
|
|
10
10
|
COMMAND_STARTED,
|
|
11
11
|
COMMAND_SUCCEEDED,
|
|
12
|
+
kDecorateResult,
|
|
12
13
|
PINNED,
|
|
13
14
|
UNPINNED
|
|
14
15
|
} from '../constants';
|
|
@@ -19,8 +20,7 @@ import {
|
|
|
19
20
|
MongoNetworkTimeoutError,
|
|
20
21
|
MongoParseError,
|
|
21
22
|
MongoServerError,
|
|
22
|
-
MongoUnexpectedServerResponseError
|
|
23
|
-
MongoWriteConcernError
|
|
23
|
+
MongoUnexpectedServerResponseError
|
|
24
24
|
} from '../error';
|
|
25
25
|
import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client';
|
|
26
26
|
import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
BufferPool,
|
|
34
34
|
calculateDurationInMs,
|
|
35
35
|
type Callback,
|
|
36
|
+
decorateDecryptionResult,
|
|
36
37
|
HostAddress,
|
|
37
38
|
maxWireVersion,
|
|
38
39
|
type MongoDBNamespace,
|
|
@@ -63,7 +64,7 @@ import { StreamDescription, type StreamDescriptionOptions } from './stream_descr
|
|
|
63
64
|
import { type CompressorName, decompressResponse } from './wire_protocol/compression';
|
|
64
65
|
import { onData } from './wire_protocol/on_data';
|
|
65
66
|
import {
|
|
66
|
-
|
|
67
|
+
CursorResponse,
|
|
67
68
|
MongoDBResponse,
|
|
68
69
|
type MongoDBResponseConstructor
|
|
69
70
|
} from './wire_protocol/responses';
|
|
@@ -448,12 +449,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
448
449
|
this.socket.setTimeout(0);
|
|
449
450
|
const bson = response.parse();
|
|
450
451
|
|
|
451
|
-
const document =
|
|
452
|
-
responseType == null
|
|
453
|
-
? new MongoDBResponse(bson)
|
|
454
|
-
: isErrorResponse(bson)
|
|
455
|
-
? new MongoDBResponse(bson)
|
|
456
|
-
: new responseType(bson);
|
|
452
|
+
const document = (responseType ?? MongoDBResponse).make(bson);
|
|
457
453
|
|
|
458
454
|
yield document;
|
|
459
455
|
this.throwIfAborted();
|
|
@@ -517,12 +513,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
517
513
|
this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
|
|
518
514
|
}
|
|
519
515
|
|
|
520
|
-
if (document.
|
|
521
|
-
object ??= document.toObject(bsonOptions);
|
|
522
|
-
throw new MongoWriteConcernError(object.writeConcernError, object);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (document.isError) {
|
|
516
|
+
if (document.ok === 0) {
|
|
526
517
|
throw new MongoServerError((object ??= document.toObject(bsonOptions)));
|
|
527
518
|
}
|
|
528
519
|
|
|
@@ -552,40 +543,25 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
552
543
|
}
|
|
553
544
|
} catch (error) {
|
|
554
545
|
if (this.shouldEmitAndLogCommand) {
|
|
555
|
-
|
|
556
|
-
this.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this,
|
|
563
|
-
message,
|
|
564
|
-
options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)),
|
|
565
|
-
started,
|
|
566
|
-
this.description.serverConnectionId
|
|
567
|
-
)
|
|
568
|
-
);
|
|
569
|
-
} else {
|
|
570
|
-
this.emitAndLogCommand(
|
|
571
|
-
this.monitorCommands,
|
|
572
|
-
Connection.COMMAND_FAILED,
|
|
573
|
-
message.databaseName,
|
|
574
|
-
this.established,
|
|
575
|
-
new CommandFailedEvent(
|
|
576
|
-
this,
|
|
577
|
-
message,
|
|
578
|
-
error,
|
|
579
|
-
started,
|
|
580
|
-
this.description.serverConnectionId
|
|
581
|
-
)
|
|
582
|
-
);
|
|
583
|
-
}
|
|
546
|
+
this.emitAndLogCommand(
|
|
547
|
+
this.monitorCommands,
|
|
548
|
+
Connection.COMMAND_FAILED,
|
|
549
|
+
message.databaseName,
|
|
550
|
+
this.established,
|
|
551
|
+
new CommandFailedEvent(this, message, error, started, this.description.serverConnectionId)
|
|
552
|
+
);
|
|
584
553
|
}
|
|
585
554
|
throw error;
|
|
586
555
|
}
|
|
587
556
|
}
|
|
588
557
|
|
|
558
|
+
public async command<T extends MongoDBResponseConstructor>(
|
|
559
|
+
ns: MongoDBNamespace,
|
|
560
|
+
command: Document,
|
|
561
|
+
options: CommandOptions | undefined,
|
|
562
|
+
responseType: T
|
|
563
|
+
): Promise<InstanceType<T>>;
|
|
564
|
+
|
|
589
565
|
public async command<T extends MongoDBResponseConstructor>(
|
|
590
566
|
ns: MongoDBNamespace,
|
|
591
567
|
command: Document,
|
|
@@ -749,7 +725,7 @@ export class CryptoConnection extends Connection {
|
|
|
749
725
|
ns: MongoDBNamespace,
|
|
750
726
|
cmd: Document,
|
|
751
727
|
options?: CommandOptions,
|
|
752
|
-
|
|
728
|
+
responseType?: T | undefined
|
|
753
729
|
): Promise<Document> {
|
|
754
730
|
const { autoEncrypter } = this;
|
|
755
731
|
if (!autoEncrypter) {
|
|
@@ -763,7 +739,7 @@ export class CryptoConnection extends Connection {
|
|
|
763
739
|
const serverWireVersion = maxWireVersion(this);
|
|
764
740
|
if (serverWireVersion === 0) {
|
|
765
741
|
// This means the initial handshake hasn't happened yet
|
|
766
|
-
return await super.command<T>(ns, cmd, options,
|
|
742
|
+
return await super.command<T>(ns, cmd, options, responseType);
|
|
767
743
|
}
|
|
768
744
|
|
|
769
745
|
if (serverWireVersion < 8) {
|
|
@@ -797,8 +773,28 @@ export class CryptoConnection extends Connection {
|
|
|
797
773
|
}
|
|
798
774
|
}
|
|
799
775
|
|
|
800
|
-
const
|
|
776
|
+
const encryptedResponse = await super.command(
|
|
777
|
+
ns,
|
|
778
|
+
encrypted,
|
|
779
|
+
options,
|
|
780
|
+
// Eventually we want to require `responseType` which means we would satisfy `T` as the return type.
|
|
781
|
+
// In the meantime, we want encryptedResponse to always be _at least_ a MongoDBResponse if not a more specific subclass
|
|
782
|
+
// So that we can ensure we have access to the on-demand APIs for decorate response
|
|
783
|
+
responseType ?? MongoDBResponse
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
const result = await autoEncrypter.decrypt(encryptedResponse.toBytes(), options);
|
|
787
|
+
|
|
788
|
+
const decryptedResponse = responseType?.make(result) ?? deserialize(result, options);
|
|
789
|
+
|
|
790
|
+
if (autoEncrypter[kDecorateResult]) {
|
|
791
|
+
if (responseType == null) {
|
|
792
|
+
decorateDecryptionResult(decryptedResponse, encryptedResponse.toObject(), true);
|
|
793
|
+
} else if (decryptedResponse instanceof CursorResponse) {
|
|
794
|
+
decryptedResponse.encryptedResponse = encryptedResponse;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
801
797
|
|
|
802
|
-
return
|
|
798
|
+
return decryptedResponse;
|
|
803
799
|
}
|
|
804
800
|
}
|
|
@@ -66,9 +66,11 @@ export class OnDemandDocument {
|
|
|
66
66
|
/** The start of the document */
|
|
67
67
|
private readonly offset = 0,
|
|
68
68
|
/** If this is an embedded document, indicates if this was a BSON array */
|
|
69
|
-
public readonly isArray = false
|
|
69
|
+
public readonly isArray = false,
|
|
70
|
+
/** If elements was already calculated */
|
|
71
|
+
elements?: BSONElement[]
|
|
70
72
|
) {
|
|
71
|
-
this.elements = parseToElementsToArray(this.bson, offset);
|
|
73
|
+
this.elements = elements ?? parseToElementsToArray(this.bson, offset);
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/** Only supports basic latin strings */
|
|
@@ -78,8 +80,13 @@ export class OnDemandDocument {
|
|
|
78
80
|
|
|
79
81
|
if (name.length !== nameLength) return false;
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
const nameEnd = nameOffset + nameLength;
|
|
84
|
+
for (
|
|
85
|
+
let byteIndex = nameOffset, charIndex = 0;
|
|
86
|
+
charIndex < name.length && byteIndex < nameEnd;
|
|
87
|
+
charIndex++, byteIndex++
|
|
88
|
+
) {
|
|
89
|
+
if (this.bson[byteIndex] !== name.charCodeAt(charIndex)) return false;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
return true;
|
|
@@ -125,7 +132,7 @@ export class OnDemandDocument {
|
|
|
125
132
|
const element = this.elements[index];
|
|
126
133
|
|
|
127
134
|
// skip this element if it has already been associated with a name
|
|
128
|
-
if (!this.indexFound
|
|
135
|
+
if (!(index in this.indexFound) && this.isElementName(name, element)) {
|
|
129
136
|
const cachedElement = { element, value: undefined };
|
|
130
137
|
this.cache[name] = cachedElement;
|
|
131
138
|
this.indexFound[index] = true;
|
|
@@ -247,7 +254,7 @@ export class OnDemandDocument {
|
|
|
247
254
|
public get<const T extends keyof JSTypeOf>(
|
|
248
255
|
name: string | number,
|
|
249
256
|
as: T,
|
|
250
|
-
required?:
|
|
257
|
+
required?: boolean | undefined
|
|
251
258
|
): JSTypeOf[T] | null;
|
|
252
259
|
|
|
253
260
|
/** `required` will make `get` throw if name does not exist or is null/undefined */
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type BSONElement,
|
|
2
3
|
type BSONSerializeOptions,
|
|
3
4
|
BSONType,
|
|
4
5
|
type Document,
|
|
5
6
|
Long,
|
|
6
7
|
parseToElementsToArray,
|
|
8
|
+
pluckBSONSerializeOptions,
|
|
7
9
|
type Timestamp
|
|
8
10
|
} from '../../bson';
|
|
9
11
|
import { MongoUnexpectedServerResponseError } from '../../error';
|
|
10
12
|
import { type ClusterTime } from '../../sdam/common';
|
|
11
|
-
import {
|
|
12
|
-
import { OnDemandDocument } from './on_demand/document';
|
|
13
|
+
import { decorateDecryptionResult, ns } from '../../utils';
|
|
14
|
+
import { type JSTypeOf, OnDemandDocument } from './on_demand/document';
|
|
13
15
|
|
|
14
16
|
// eslint-disable-next-line no-restricted-syntax
|
|
15
17
|
const enum BSONElementOffset {
|
|
@@ -30,8 +32,7 @@ const enum BSONElementOffset {
|
|
|
30
32
|
*
|
|
31
33
|
* @param bytes - BSON document returned from the server
|
|
32
34
|
*/
|
|
33
|
-
export function isErrorResponse(bson: Uint8Array): boolean {
|
|
34
|
-
const elements = parseToElementsToArray(bson, 0);
|
|
35
|
+
export function isErrorResponse(bson: Uint8Array, elements: BSONElement[]): boolean {
|
|
35
36
|
for (let eIdx = 0; eIdx < elements.length; eIdx++) {
|
|
36
37
|
const element = elements[eIdx];
|
|
37
38
|
|
|
@@ -60,26 +61,49 @@ export function isErrorResponse(bson: Uint8Array): boolean {
|
|
|
60
61
|
/** @internal */
|
|
61
62
|
export type MongoDBResponseConstructor = {
|
|
62
63
|
new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
|
|
64
|
+
make(bson: Uint8Array): MongoDBResponse;
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
/** @internal */
|
|
66
68
|
export class MongoDBResponse extends OnDemandDocument {
|
|
69
|
+
// Wrap error thrown from BSON
|
|
70
|
+
public override get<const T extends keyof JSTypeOf>(
|
|
71
|
+
name: string | number,
|
|
72
|
+
as: T,
|
|
73
|
+
required?: false | undefined
|
|
74
|
+
): JSTypeOf[T] | null;
|
|
75
|
+
public override get<const T extends keyof JSTypeOf>(
|
|
76
|
+
name: string | number,
|
|
77
|
+
as: T,
|
|
78
|
+
required: true
|
|
79
|
+
): JSTypeOf[T];
|
|
80
|
+
public override get<const T extends keyof JSTypeOf>(
|
|
81
|
+
name: string | number,
|
|
82
|
+
as: T,
|
|
83
|
+
required?: boolean | undefined
|
|
84
|
+
): JSTypeOf[T] | null {
|
|
85
|
+
try {
|
|
86
|
+
return super.get(name, as, required);
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
throw new MongoUnexpectedServerResponseError(cause.message, { cause });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
67
92
|
static is(value: unknown): value is MongoDBResponse {
|
|
68
93
|
return value instanceof MongoDBResponse;
|
|
69
94
|
}
|
|
70
95
|
|
|
96
|
+
static make(bson: Uint8Array) {
|
|
97
|
+
const elements = parseToElementsToArray(bson, 0);
|
|
98
|
+
const isError = isErrorResponse(bson, elements);
|
|
99
|
+
return isError
|
|
100
|
+
? new MongoDBResponse(bson, 0, false, elements)
|
|
101
|
+
: new this(bson, 0, false, elements);
|
|
102
|
+
}
|
|
103
|
+
|
|
71
104
|
// {ok:1}
|
|
72
105
|
static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
|
|
73
106
|
|
|
74
|
-
/** Indicates this document is a server error */
|
|
75
|
-
public get isError() {
|
|
76
|
-
let isError = this.ok === 0;
|
|
77
|
-
isError ||= this.has('errmsg');
|
|
78
|
-
isError ||= this.has('code');
|
|
79
|
-
isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
|
|
80
|
-
return isError;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
107
|
/**
|
|
84
108
|
* Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the
|
|
85
109
|
* contents of the document.
|
|
@@ -110,6 +134,7 @@ export class MongoDBResponse extends OnDemandDocument {
|
|
|
110
134
|
return this.get('operationTime', BSONType.timestamp);
|
|
111
135
|
}
|
|
112
136
|
|
|
137
|
+
/** Normalizes whatever BSON value is "ok" to a JS number 1 or 0. */
|
|
113
138
|
public get ok(): 0 | 1 {
|
|
114
139
|
return this.getNumber('ok') ? 1 : 0;
|
|
115
140
|
}
|
|
@@ -144,13 +169,7 @@ export class MongoDBResponse extends OnDemandDocument {
|
|
|
144
169
|
|
|
145
170
|
public override toObject(options?: BSONSerializeOptions): Record<string, any> {
|
|
146
171
|
const exactBSONOptions = {
|
|
147
|
-
|
|
148
|
-
promoteLongs: options?.promoteLongs,
|
|
149
|
-
promoteValues: options?.promoteValues,
|
|
150
|
-
promoteBuffers: options?.promoteBuffers,
|
|
151
|
-
bsonRegExp: options?.bsonRegExp,
|
|
152
|
-
raw: options?.raw ?? false,
|
|
153
|
-
fieldsAsRaw: options?.fieldsAsRaw ?? {},
|
|
172
|
+
...pluckBSONSerializeOptions(options ?? {}),
|
|
154
173
|
validation: this.parseBsonSerializationOptions(options)
|
|
155
174
|
};
|
|
156
175
|
return super.toObject(exactBSONOptions);
|
|
@@ -169,69 +188,145 @@ export class MongoDBResponse extends OnDemandDocument {
|
|
|
169
188
|
|
|
170
189
|
/** @internal */
|
|
171
190
|
export class CursorResponse extends MongoDBResponse {
|
|
191
|
+
/**
|
|
192
|
+
* Devtools need to know which keys were encrypted before the driver automatically decrypted them.
|
|
193
|
+
* If decorating is enabled (`Symbol.for('@@mdb.decorateDecryptionResult')`), this field will be set,
|
|
194
|
+
* storing the original encrypted response from the server, so that we can build an object that has
|
|
195
|
+
* the list of BSON keys that were encrypted stored at a well known symbol: `Symbol.for('@@mdb.decryptedKeys')`.
|
|
196
|
+
*/
|
|
197
|
+
encryptedResponse?: MongoDBResponse;
|
|
172
198
|
/**
|
|
173
199
|
* This supports a feature of the FindCursor.
|
|
174
200
|
* It is an optimization to avoid an extra getMore when the limit has been reached
|
|
175
201
|
*/
|
|
176
|
-
static emptyGetMore = {
|
|
202
|
+
static emptyGetMore: CursorResponse = {
|
|
203
|
+
id: new Long(0),
|
|
204
|
+
length: 0,
|
|
205
|
+
shift: () => null
|
|
206
|
+
} as unknown as CursorResponse;
|
|
177
207
|
|
|
178
208
|
static override is(value: unknown): value is CursorResponse {
|
|
179
209
|
return value instanceof CursorResponse || value === CursorResponse.emptyGetMore;
|
|
180
210
|
}
|
|
181
211
|
|
|
182
|
-
|
|
183
|
-
public ns: MongoDBNamespace | null = null;
|
|
184
|
-
public batchSize = 0;
|
|
185
|
-
|
|
186
|
-
private batch: OnDemandDocument;
|
|
212
|
+
private _batch: OnDemandDocument | null = null;
|
|
187
213
|
private iterated = 0;
|
|
188
214
|
|
|
189
|
-
|
|
190
|
-
|
|
215
|
+
get cursor() {
|
|
216
|
+
return this.get('cursor', BSONType.object, true);
|
|
217
|
+
}
|
|
191
218
|
|
|
192
|
-
|
|
219
|
+
public get id(): Long {
|
|
220
|
+
try {
|
|
221
|
+
return Long.fromBigInt(this.cursor.get('id', BSONType.long, true));
|
|
222
|
+
} catch (cause) {
|
|
223
|
+
throw new MongoUnexpectedServerResponseError(cause.message, { cause });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
193
226
|
|
|
194
|
-
|
|
195
|
-
|
|
227
|
+
public get ns() {
|
|
228
|
+
const namespace = this.cursor.get('ns', BSONType.string);
|
|
229
|
+
if (namespace != null) return ns(namespace);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public get length() {
|
|
234
|
+
return Math.max(this.batchSize - this.iterated, 0);
|
|
235
|
+
}
|
|
196
236
|
|
|
197
|
-
|
|
198
|
-
|
|
237
|
+
private _encryptedBatch: OnDemandDocument | null = null;
|
|
238
|
+
get encryptedBatch() {
|
|
239
|
+
if (this.encryptedResponse == null) return null;
|
|
240
|
+
if (this._encryptedBatch != null) return this._encryptedBatch;
|
|
199
241
|
|
|
200
|
-
|
|
201
|
-
|
|
242
|
+
const cursor = this.encryptedResponse?.get('cursor', BSONType.object);
|
|
243
|
+
if (cursor?.has('firstBatch'))
|
|
244
|
+
this._encryptedBatch = cursor.get('firstBatch', BSONType.array, true);
|
|
245
|
+
else if (cursor?.has('nextBatch'))
|
|
246
|
+
this._encryptedBatch = cursor.get('nextBatch', BSONType.array, true);
|
|
202
247
|
else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
|
|
203
248
|
|
|
204
|
-
|
|
249
|
+
return this._encryptedBatch;
|
|
205
250
|
}
|
|
206
251
|
|
|
207
|
-
get
|
|
208
|
-
|
|
252
|
+
private get batch() {
|
|
253
|
+
if (this._batch != null) return this._batch;
|
|
254
|
+
const cursor = this.cursor;
|
|
255
|
+
if (cursor.has('firstBatch')) this._batch = cursor.get('firstBatch', BSONType.array, true);
|
|
256
|
+
else if (cursor.has('nextBatch')) this._batch = cursor.get('nextBatch', BSONType.array, true);
|
|
257
|
+
else throw new MongoUnexpectedServerResponseError('Cursor document did not contain a batch');
|
|
258
|
+
return this._batch;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public get batchSize() {
|
|
262
|
+
return this.batch?.size();
|
|
209
263
|
}
|
|
210
264
|
|
|
211
|
-
|
|
265
|
+
public get postBatchResumeToken() {
|
|
266
|
+
return (
|
|
267
|
+
this.cursor.get('postBatchResumeToken', BSONType.object)?.toObject({
|
|
268
|
+
promoteValues: false,
|
|
269
|
+
promoteLongs: false,
|
|
270
|
+
promoteBuffers: false
|
|
271
|
+
}) ?? null
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public shift(options?: BSONSerializeOptions): any {
|
|
212
276
|
if (this.iterated >= this.batchSize) {
|
|
213
277
|
return null;
|
|
214
278
|
}
|
|
215
279
|
|
|
216
280
|
const result = this.batch.get(this.iterated, BSONType.object, true) ?? null;
|
|
281
|
+
const encryptedResult = this.encryptedBatch?.get(this.iterated, BSONType.object, true) ?? null;
|
|
282
|
+
|
|
217
283
|
this.iterated += 1;
|
|
218
284
|
|
|
219
285
|
if (options?.raw) {
|
|
220
286
|
return result.toBytes();
|
|
221
287
|
} else {
|
|
222
|
-
|
|
288
|
+
const object = result.toObject(options);
|
|
289
|
+
if (encryptedResult) {
|
|
290
|
+
decorateDecryptionResult(object, encryptedResult.toObject(options), true);
|
|
291
|
+
}
|
|
292
|
+
return object;
|
|
223
293
|
}
|
|
224
294
|
}
|
|
225
295
|
|
|
226
|
-
clear() {
|
|
296
|
+
public clear() {
|
|
227
297
|
this.iterated = this.batchSize;
|
|
228
298
|
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Explain responses have nothing to do with cursor responses
|
|
303
|
+
* This class serves to temporarily avoid refactoring how cursors handle
|
|
304
|
+
* explain responses which is to detect that the response is not cursor-like and return the explain
|
|
305
|
+
* result as the "first and only" document in the "batch" and end the "cursor"
|
|
306
|
+
*/
|
|
307
|
+
export class ExplainedCursorResponse extends CursorResponse {
|
|
308
|
+
isExplain = true;
|
|
309
|
+
|
|
310
|
+
override get id(): Long {
|
|
311
|
+
return Long.fromBigInt(0n);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
override get batchSize() {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
override get ns() {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
229
321
|
|
|
230
|
-
|
|
231
|
-
|
|
322
|
+
_length = 1;
|
|
323
|
+
override get length(): number {
|
|
324
|
+
return this._length;
|
|
232
325
|
}
|
|
233
326
|
|
|
234
|
-
|
|
235
|
-
|
|
327
|
+
override shift(options?: BSONSerializeOptions | undefined) {
|
|
328
|
+
if (this._length === 0) return null;
|
|
329
|
+
this._length -= 1;
|
|
330
|
+
return this.toObject(options);
|
|
236
331
|
}
|
|
237
332
|
}
|
package/src/collection.ts
CHANGED
|
@@ -25,7 +25,6 @@ import type {
|
|
|
25
25
|
import type { AggregateOptions } from './operations/aggregate';
|
|
26
26
|
import { BulkWriteOperation } from './operations/bulk_write';
|
|
27
27
|
import { CountOperation, type CountOptions } from './operations/count';
|
|
28
|
-
import { CountDocumentsOperation, type CountDocumentsOptions } from './operations/count_documents';
|
|
29
28
|
import {
|
|
30
29
|
DeleteManyOperation,
|
|
31
30
|
DeleteOneOperation,
|
|
@@ -101,6 +100,14 @@ export interface ModifyResult<TSchema = Document> {
|
|
|
101
100
|
ok: 0 | 1;
|
|
102
101
|
}
|
|
103
102
|
|
|
103
|
+
/** @public */
|
|
104
|
+
export interface CountDocumentsOptions extends AggregateOptions {
|
|
105
|
+
/** The number of documents to skip. */
|
|
106
|
+
skip?: number;
|
|
107
|
+
/** The maximum amount of documents to consider. */
|
|
108
|
+
limit?: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
104
111
|
/** @public */
|
|
105
112
|
export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOptions {
|
|
106
113
|
/** Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) */
|
|
@@ -764,10 +771,23 @@ export class Collection<TSchema extends Document = Document> {
|
|
|
764
771
|
filter: Filter<TSchema> = {},
|
|
765
772
|
options: CountDocumentsOptions = {}
|
|
766
773
|
): Promise<number> {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
)
|
|
774
|
+
const pipeline = [];
|
|
775
|
+
pipeline.push({ $match: filter });
|
|
776
|
+
|
|
777
|
+
if (typeof options.skip === 'number') {
|
|
778
|
+
pipeline.push({ $skip: options.skip });
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (typeof options.limit === 'number') {
|
|
782
|
+
pipeline.push({ $limit: options.limit });
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
pipeline.push({ $group: { _id: 1, n: { $sum: 1 } } });
|
|
786
|
+
|
|
787
|
+
const cursor = this.aggregate<{ n: number }>(pipeline, options);
|
|
788
|
+
const doc = await cursor.next();
|
|
789
|
+
await cursor.close();
|
|
790
|
+
return doc?.n ?? 0;
|
|
771
791
|
}
|
|
772
792
|
|
|
773
793
|
/**
|
package/src/constants.ts
CHANGED
|
@@ -165,3 +165,12 @@ export const LEGACY_HELLO_COMMAND = 'ismaster';
|
|
|
165
165
|
* The legacy hello command that was deprecated in MongoDB 5.0.
|
|
166
166
|
*/
|
|
167
167
|
export const LEGACY_HELLO_COMMAND_CAMEL_CASE = 'isMaster';
|
|
168
|
+
|
|
169
|
+
// Typescript errors if we index objects with `Symbol.for(...)`, so
|
|
170
|
+
// to avoid TS errors we pull them out into variables. Then we can type
|
|
171
|
+
// the objects (and class) that we expect to see them on and prevent TS
|
|
172
|
+
// errors.
|
|
173
|
+
/** @internal */
|
|
174
|
+
export const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
|
|
175
|
+
/** @internal */
|
|
176
|
+
export const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
|