mongodb 6.5.0-dev.20240406.sha.62ea94b → 6.5.0-dev.20240409.sha.30cac05
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/bson.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +2 -1
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/commands.js +24 -111
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +1 -1
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +46 -31
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/wire_protocol/compression.js +2 -2
- package/lib/cmap/wire_protocol/compression.js.map +1 -1
- package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
- package/lib/cmap/wire_protocol/responses.js +88 -0
- package/lib/cmap/wire_protocol/responses.js.map +1 -0
- package/lib/index.js.map +1 -1
- package/lib/sessions.js +1 -1
- package/lib/sessions.js.map +1 -1
- package/mongodb.d.ts +9 -3
- package/package.json +1 -1
- package/src/bson.ts +1 -0
- package/src/cmap/auth/gssapi.ts +3 -5
- package/src/cmap/commands.ts +28 -132
- package/src/cmap/connect.ts +1 -1
- package/src/cmap/connection.ts +86 -38
- package/src/cmap/wire_protocol/compression.ts +4 -6
- package/src/cmap/wire_protocol/on_demand/document.ts +1 -0
- package/src/cmap/wire_protocol/responses.ts +109 -0
- package/src/index.ts +3 -2
- package/src/sessions.ts +3 -2
package/src/cmap/commands.ts
CHANGED
|
@@ -37,7 +37,6 @@ export type WriteProtocolMessageType = OpQueryRequest | OpMsgRequest;
|
|
|
37
37
|
export interface OpQueryOptions extends CommandOptions {
|
|
38
38
|
socketTimeoutMS?: number;
|
|
39
39
|
session?: ClientSession;
|
|
40
|
-
documentsReturnedIn?: string;
|
|
41
40
|
numberToSkip?: number;
|
|
42
41
|
numberToReturn?: number;
|
|
43
42
|
returnFieldSelector?: Document;
|
|
@@ -53,9 +52,6 @@ export interface OpQueryOptions extends CommandOptions {
|
|
|
53
52
|
exhaustAllowed?: boolean;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
/**************************************************************
|
|
57
|
-
* QUERY
|
|
58
|
-
**************************************************************/
|
|
59
55
|
/** @internal */
|
|
60
56
|
export class OpQueryRequest {
|
|
61
57
|
ns: string;
|
|
@@ -284,16 +280,11 @@ export interface MessageHeader {
|
|
|
284
280
|
}
|
|
285
281
|
|
|
286
282
|
/** @internal */
|
|
287
|
-
export
|
|
288
|
-
documentsReturnedIn?: string | null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** @internal */
|
|
292
|
-
export class OpQueryResponse {
|
|
283
|
+
export class OpReply {
|
|
293
284
|
parsed: boolean;
|
|
294
285
|
raw: Buffer;
|
|
295
286
|
data: Buffer;
|
|
296
|
-
opts:
|
|
287
|
+
opts: BSONSerializeOptions;
|
|
297
288
|
length: number;
|
|
298
289
|
requestId: number;
|
|
299
290
|
responseTo: number;
|
|
@@ -303,7 +294,6 @@ export class OpQueryResponse {
|
|
|
303
294
|
cursorId?: Long;
|
|
304
295
|
startingFrom?: number;
|
|
305
296
|
numberReturned?: number;
|
|
306
|
-
documents: (Document | Buffer)[] = new Array(0);
|
|
307
297
|
cursorNotFound?: boolean;
|
|
308
298
|
queryFailure?: boolean;
|
|
309
299
|
shardConfigStale?: boolean;
|
|
@@ -313,7 +303,8 @@ export class OpQueryResponse {
|
|
|
313
303
|
promoteValues: boolean;
|
|
314
304
|
promoteBuffers: boolean;
|
|
315
305
|
bsonRegExp?: boolean;
|
|
316
|
-
index
|
|
306
|
+
index = 0;
|
|
307
|
+
sections: Uint8Array[] = [];
|
|
317
308
|
|
|
318
309
|
/** moreToCome is an OP_MSG only concept */
|
|
319
310
|
moreToCome = false;
|
|
@@ -322,7 +313,7 @@ export class OpQueryResponse {
|
|
|
322
313
|
message: Buffer,
|
|
323
314
|
msgHeader: MessageHeader,
|
|
324
315
|
msgBody: Buffer,
|
|
325
|
-
opts?:
|
|
316
|
+
opts?: BSONSerializeOptions
|
|
326
317
|
) {
|
|
327
318
|
this.parsed = false;
|
|
328
319
|
this.raw = message;
|
|
@@ -356,29 +347,9 @@ export class OpQueryResponse {
|
|
|
356
347
|
return this.parsed;
|
|
357
348
|
}
|
|
358
349
|
|
|
359
|
-
parse(
|
|
350
|
+
parse(): Uint8Array {
|
|
360
351
|
// Don't parse again if not needed
|
|
361
|
-
if (this.parsed) return;
|
|
362
|
-
options = options ?? {};
|
|
363
|
-
|
|
364
|
-
// Allow the return of raw documents instead of parsing
|
|
365
|
-
const raw = options.raw || false;
|
|
366
|
-
const documentsReturnedIn = options.documentsReturnedIn || null;
|
|
367
|
-
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
|
|
368
|
-
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
|
|
369
|
-
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
|
|
370
|
-
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
|
|
371
|
-
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
|
|
372
|
-
let bsonSize;
|
|
373
|
-
|
|
374
|
-
// Set up the options
|
|
375
|
-
const _options: BSONSerializeOptions = {
|
|
376
|
-
useBigInt64,
|
|
377
|
-
promoteLongs,
|
|
378
|
-
promoteValues,
|
|
379
|
-
promoteBuffers,
|
|
380
|
-
bsonRegExp
|
|
381
|
-
};
|
|
352
|
+
if (this.parsed) return this.sections[0];
|
|
382
353
|
|
|
383
354
|
// Position within OP_REPLY at which documents start
|
|
384
355
|
// (See https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/#wire-op-reply)
|
|
@@ -390,8 +361,11 @@ export class OpQueryResponse {
|
|
|
390
361
|
this.startingFrom = this.data.readInt32LE(12);
|
|
391
362
|
this.numberReturned = this.data.readInt32LE(16);
|
|
392
363
|
|
|
393
|
-
|
|
394
|
-
|
|
364
|
+
if (this.numberReturned < 0 || this.numberReturned > 2 ** 32 - 1) {
|
|
365
|
+
throw new RangeError(
|
|
366
|
+
`OP_REPLY numberReturned is an invalid array length ${this.numberReturned}`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
395
369
|
|
|
396
370
|
this.cursorNotFound = (this.responseFlags & CURSOR_NOT_FOUND) !== 0;
|
|
397
371
|
this.queryFailure = (this.responseFlags & QUERY_FAILURE) !== 0;
|
|
@@ -400,67 +374,26 @@ export class OpQueryResponse {
|
|
|
400
374
|
|
|
401
375
|
// Parse Body
|
|
402
376
|
for (let i = 0; i < this.numberReturned; i++) {
|
|
403
|
-
bsonSize =
|
|
377
|
+
const bsonSize =
|
|
404
378
|
this.data[this.index] |
|
|
405
379
|
(this.data[this.index + 1] << 8) |
|
|
406
380
|
(this.data[this.index + 2] << 16) |
|
|
407
381
|
(this.data[this.index + 3] << 24);
|
|
408
382
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.documents[i] = this.data.slice(this.index, this.index + bsonSize);
|
|
412
|
-
} else {
|
|
413
|
-
this.documents[i] = BSON.deserialize(
|
|
414
|
-
this.data.slice(this.index, this.index + bsonSize),
|
|
415
|
-
_options
|
|
416
|
-
);
|
|
417
|
-
}
|
|
383
|
+
const section = this.data.subarray(this.index, this.index + bsonSize);
|
|
384
|
+
this.sections.push(section);
|
|
418
385
|
|
|
419
386
|
// Adjust the index
|
|
420
387
|
this.index = this.index + bsonSize;
|
|
421
388
|
}
|
|
422
389
|
|
|
423
|
-
if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
|
|
424
|
-
const fieldsAsRaw: Document = {};
|
|
425
|
-
fieldsAsRaw[documentsReturnedIn] = true;
|
|
426
|
-
_options.fieldsAsRaw = fieldsAsRaw;
|
|
427
|
-
|
|
428
|
-
const doc = BSON.deserialize(this.documents[0] as Buffer, _options);
|
|
429
|
-
this.documents = [doc];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
390
|
// Set parsed
|
|
433
391
|
this.parsed = true;
|
|
392
|
+
|
|
393
|
+
return this.sections[0];
|
|
434
394
|
}
|
|
435
395
|
}
|
|
436
396
|
|
|
437
|
-
// Implementation of OP_MSG spec:
|
|
438
|
-
// https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.rst
|
|
439
|
-
//
|
|
440
|
-
// struct Section {
|
|
441
|
-
// uint8 payloadType;
|
|
442
|
-
// union payload {
|
|
443
|
-
// document document; // payloadType == 0
|
|
444
|
-
// struct sequence { // payloadType == 1
|
|
445
|
-
// int32 size;
|
|
446
|
-
// cstring identifier;
|
|
447
|
-
// document* documents;
|
|
448
|
-
// };
|
|
449
|
-
// };
|
|
450
|
-
// };
|
|
451
|
-
|
|
452
|
-
// struct OP_MSG {
|
|
453
|
-
// struct MsgHeader {
|
|
454
|
-
// int32 messageLength;
|
|
455
|
-
// int32 requestID;
|
|
456
|
-
// int32 responseTo;
|
|
457
|
-
// int32 opCode = 2013;
|
|
458
|
-
// };
|
|
459
|
-
// uint32 flagBits;
|
|
460
|
-
// Section+ sections;
|
|
461
|
-
// [uint32 checksum;]
|
|
462
|
-
// };
|
|
463
|
-
|
|
464
397
|
// Msg Flags
|
|
465
398
|
const OPTS_CHECKSUM_PRESENT = 1;
|
|
466
399
|
const OPTS_MORE_TO_COME = 2;
|
|
@@ -587,7 +520,7 @@ export class OpMsgResponse {
|
|
|
587
520
|
parsed: boolean;
|
|
588
521
|
raw: Buffer;
|
|
589
522
|
data: Buffer;
|
|
590
|
-
opts:
|
|
523
|
+
opts: BSONSerializeOptions;
|
|
591
524
|
length: number;
|
|
592
525
|
requestId: number;
|
|
593
526
|
responseTo: number;
|
|
@@ -603,14 +536,14 @@ export class OpMsgResponse {
|
|
|
603
536
|
promoteValues: boolean;
|
|
604
537
|
promoteBuffers: boolean;
|
|
605
538
|
bsonRegExp: boolean;
|
|
606
|
-
|
|
607
|
-
|
|
539
|
+
index = 0;
|
|
540
|
+
sections: Uint8Array[] = [];
|
|
608
541
|
|
|
609
542
|
constructor(
|
|
610
543
|
message: Buffer,
|
|
611
544
|
msgHeader: MessageHeader,
|
|
612
545
|
msgBody: Buffer,
|
|
613
|
-
opts?:
|
|
546
|
+
opts?: BSONSerializeOptions
|
|
614
547
|
) {
|
|
615
548
|
this.parsed = false;
|
|
616
549
|
this.raw = message;
|
|
@@ -642,47 +575,26 @@ export class OpMsgResponse {
|
|
|
642
575
|
this.promoteBuffers =
|
|
643
576
|
typeof this.opts.promoteBuffers === 'boolean' ? this.opts.promoteBuffers : false;
|
|
644
577
|
this.bsonRegExp = typeof this.opts.bsonRegExp === 'boolean' ? this.opts.bsonRegExp : false;
|
|
645
|
-
|
|
646
|
-
this.documents = [];
|
|
647
578
|
}
|
|
648
579
|
|
|
649
580
|
isParsed(): boolean {
|
|
650
581
|
return this.parsed;
|
|
651
582
|
}
|
|
652
583
|
|
|
653
|
-
parse(
|
|
584
|
+
parse(): Uint8Array {
|
|
654
585
|
// Don't parse again if not needed
|
|
655
|
-
if (this.parsed) return;
|
|
656
|
-
options = options ?? {};
|
|
586
|
+
if (this.parsed) return this.sections[0];
|
|
657
587
|
|
|
658
588
|
this.index = 4;
|
|
659
|
-
// Allow the return of raw documents instead of parsing
|
|
660
|
-
const raw = options.raw || false;
|
|
661
|
-
const documentsReturnedIn = options.documentsReturnedIn || null;
|
|
662
|
-
const useBigInt64 = options.useBigInt64 ?? this.opts.useBigInt64;
|
|
663
|
-
const promoteLongs = options.promoteLongs ?? this.opts.promoteLongs;
|
|
664
|
-
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
|
|
665
|
-
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
|
|
666
|
-
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
|
|
667
|
-
const validation = this.parseBsonSerializationOptions(options);
|
|
668
|
-
|
|
669
|
-
// Set up the options
|
|
670
|
-
const bsonOptions: BSONSerializeOptions = {
|
|
671
|
-
useBigInt64,
|
|
672
|
-
promoteLongs,
|
|
673
|
-
promoteValues,
|
|
674
|
-
promoteBuffers,
|
|
675
|
-
bsonRegExp,
|
|
676
|
-
validation
|
|
677
|
-
// Due to the strictness of the BSON libraries validation option we need this cast
|
|
678
|
-
} as BSONSerializeOptions & { validation: { utf8: { writeErrors: boolean } } };
|
|
679
589
|
|
|
680
590
|
while (this.index < this.data.length) {
|
|
681
591
|
const payloadType = this.data.readUInt8(this.index++);
|
|
682
592
|
if (payloadType === 0) {
|
|
683
593
|
const bsonSize = this.data.readUInt32LE(this.index);
|
|
684
|
-
const bin = this.data.
|
|
685
|
-
|
|
594
|
+
const bin = this.data.subarray(this.index, this.index + bsonSize);
|
|
595
|
+
|
|
596
|
+
this.sections.push(bin);
|
|
597
|
+
|
|
686
598
|
this.index += bsonSize;
|
|
687
599
|
} else if (payloadType === 1) {
|
|
688
600
|
// It was decided that no driver makes use of payload type 1
|
|
@@ -692,25 +604,9 @@ export class OpMsgResponse {
|
|
|
692
604
|
}
|
|
693
605
|
}
|
|
694
606
|
|
|
695
|
-
if (this.documents.length === 1 && documentsReturnedIn != null && raw) {
|
|
696
|
-
const fieldsAsRaw: Document = {};
|
|
697
|
-
fieldsAsRaw[documentsReturnedIn] = true;
|
|
698
|
-
bsonOptions.fieldsAsRaw = fieldsAsRaw;
|
|
699
|
-
const doc = BSON.deserialize(this.documents[0] as Buffer, bsonOptions);
|
|
700
|
-
this.documents = [doc];
|
|
701
|
-
}
|
|
702
|
-
|
|
703
607
|
this.parsed = true;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
|
|
707
|
-
utf8: { writeErrors: false } | false;
|
|
708
|
-
} {
|
|
709
|
-
if (enableUtf8Validation === false) {
|
|
710
|
-
return { utf8: false };
|
|
711
|
-
}
|
|
712
608
|
|
|
713
|
-
return
|
|
609
|
+
return this.sections[0];
|
|
714
610
|
}
|
|
715
611
|
}
|
|
716
612
|
|
package/src/cmap/connect.ts
CHANGED
|
@@ -103,7 +103,7 @@ export async function performInitialHandshake(
|
|
|
103
103
|
const handshakeDoc = await prepareHandshakeDocument(authContext);
|
|
104
104
|
|
|
105
105
|
// @ts-expect-error: TODO(NODE-5141): The options need to be filtered properly, Connection options differ from Command options
|
|
106
|
-
const handshakeOptions: CommandOptions = { ...options };
|
|
106
|
+
const handshakeOptions: CommandOptions = { ...options, raw: false };
|
|
107
107
|
if (typeof options.connectTimeoutMS === 'number') {
|
|
108
108
|
// The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
|
|
109
109
|
handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
|
package/src/cmap/connection.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
OpMsgRequest,
|
|
55
55
|
type OpMsgResponse,
|
|
56
56
|
OpQueryRequest,
|
|
57
|
-
type
|
|
57
|
+
type OpReply,
|
|
58
58
|
type WriteProtocolMessageType
|
|
59
59
|
} from './commands';
|
|
60
60
|
import type { Stream } from './connect';
|
|
@@ -62,6 +62,7 @@ import type { ClientMetadata } from './handshake/client_metadata';
|
|
|
62
62
|
import { StreamDescription, type StreamDescriptionOptions } from './stream_description';
|
|
63
63
|
import { type CompressorName, decompressResponse } from './wire_protocol/compression';
|
|
64
64
|
import { onData } from './wire_protocol/on_data';
|
|
65
|
+
import { MongoDBResponse, type MongoDBResponseConstructor } from './wire_protocol/responses';
|
|
65
66
|
import { getReadPreference, isSharded } from './wire_protocol/shared';
|
|
66
67
|
|
|
67
68
|
/** @internal */
|
|
@@ -412,7 +413,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
412
413
|
return message;
|
|
413
414
|
}
|
|
414
415
|
|
|
415
|
-
private async *sendWire(
|
|
416
|
+
private async *sendWire(
|
|
417
|
+
message: WriteProtocolMessageType,
|
|
418
|
+
options: CommandOptions,
|
|
419
|
+
responseType?: MongoDBResponseConstructor
|
|
420
|
+
): AsyncGenerator<MongoDBResponse> {
|
|
416
421
|
this.throwIfAborted();
|
|
417
422
|
|
|
418
423
|
if (typeof options.socketTimeoutMS === 'number') {
|
|
@@ -428,7 +433,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
428
433
|
});
|
|
429
434
|
|
|
430
435
|
if (options.noResponse) {
|
|
431
|
-
yield
|
|
436
|
+
yield MongoDBResponse.empty;
|
|
432
437
|
return;
|
|
433
438
|
}
|
|
434
439
|
|
|
@@ -436,21 +441,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
436
441
|
|
|
437
442
|
for await (const response of this.readMany()) {
|
|
438
443
|
this.socket.setTimeout(0);
|
|
439
|
-
response.parse(
|
|
440
|
-
|
|
441
|
-
const [document] = response.documents;
|
|
444
|
+
const bson = response.parse();
|
|
442
445
|
|
|
443
|
-
|
|
444
|
-
const { session } = options;
|
|
445
|
-
if (session) {
|
|
446
|
-
updateSessionFromResponse(session, document);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if (document.$clusterTime) {
|
|
450
|
-
this.clusterTime = document.$clusterTime;
|
|
451
|
-
this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
446
|
+
const document = new (responseType ?? MongoDBResponse)(bson, 0, false);
|
|
454
447
|
|
|
455
448
|
yield document;
|
|
456
449
|
this.throwIfAborted();
|
|
@@ -469,7 +462,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
469
462
|
private async *sendCommand(
|
|
470
463
|
ns: MongoDBNamespace,
|
|
471
464
|
command: Document,
|
|
472
|
-
options: CommandOptions
|
|
465
|
+
options: CommandOptions,
|
|
466
|
+
responseType?: MongoDBResponseConstructor
|
|
473
467
|
) {
|
|
474
468
|
const message = this.prepareCommand(ns.db, command, options);
|
|
475
469
|
|
|
@@ -485,19 +479,41 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
485
479
|
);
|
|
486
480
|
}
|
|
487
481
|
|
|
488
|
-
|
|
482
|
+
// If `documentsReturnedIn` not set or raw is not enabled, use input bson options
|
|
483
|
+
// Otherwise, support raw flag. Raw only works for cursors that hardcode firstBatch/nextBatch fields
|
|
484
|
+
const bsonOptions =
|
|
485
|
+
options.documentsReturnedIn == null || !options.raw
|
|
486
|
+
? options
|
|
487
|
+
: {
|
|
488
|
+
...options,
|
|
489
|
+
raw: false,
|
|
490
|
+
fieldsAsRaw: { [options.documentsReturnedIn]: true }
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/** MongoDBResponse instance or subclass */
|
|
494
|
+
let document: MongoDBResponse | undefined = undefined;
|
|
495
|
+
/** Cached result of a toObject call */
|
|
496
|
+
let object: Document | undefined = undefined;
|
|
489
497
|
try {
|
|
490
498
|
this.throwIfAborted();
|
|
491
|
-
for await (document of this.sendWire(message, options)) {
|
|
492
|
-
|
|
493
|
-
|
|
499
|
+
for await (document of this.sendWire(message, options, responseType)) {
|
|
500
|
+
object = undefined;
|
|
501
|
+
if (options.session != null) {
|
|
502
|
+
updateSessionFromResponse(options.session, document);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (document.$clusterTime) {
|
|
506
|
+
this.clusterTime = document.$clusterTime;
|
|
507
|
+
this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
|
|
494
508
|
}
|
|
495
509
|
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
510
|
+
if (document.has('writeConcernError')) {
|
|
511
|
+
object ??= document.toObject(bsonOptions);
|
|
512
|
+
throw new MongoWriteConcernError(object.writeConcernError, object);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (document.isError) {
|
|
516
|
+
throw new MongoServerError((object ??= document.toObject(bsonOptions)));
|
|
501
517
|
}
|
|
502
518
|
|
|
503
519
|
if (this.shouldEmitAndLogCommand) {
|
|
@@ -509,14 +525,19 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
509
525
|
new CommandSucceededEvent(
|
|
510
526
|
this,
|
|
511
527
|
message,
|
|
512
|
-
options.noResponse ? undefined : document,
|
|
528
|
+
options.noResponse ? undefined : (object ??= document.toObject(bsonOptions)),
|
|
513
529
|
started,
|
|
514
530
|
this.description.serverConnectionId
|
|
515
531
|
)
|
|
516
532
|
);
|
|
517
533
|
}
|
|
518
534
|
|
|
519
|
-
|
|
535
|
+
if (responseType == null) {
|
|
536
|
+
yield (object ??= document.toObject(bsonOptions));
|
|
537
|
+
} else {
|
|
538
|
+
yield document;
|
|
539
|
+
}
|
|
540
|
+
|
|
520
541
|
this.throwIfAborted();
|
|
521
542
|
}
|
|
522
543
|
} catch (error) {
|
|
@@ -530,7 +551,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
530
551
|
new CommandSucceededEvent(
|
|
531
552
|
this,
|
|
532
553
|
message,
|
|
533
|
-
options.noResponse ? undefined : document,
|
|
554
|
+
options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)),
|
|
534
555
|
started,
|
|
535
556
|
this.description.serverConnectionId
|
|
536
557
|
)
|
|
@@ -555,13 +576,27 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
555
576
|
}
|
|
556
577
|
}
|
|
557
578
|
|
|
579
|
+
public async command<T extends MongoDBResponseConstructor>(
|
|
580
|
+
ns: MongoDBNamespace,
|
|
581
|
+
command: Document,
|
|
582
|
+
options: CommandOptions | undefined,
|
|
583
|
+
responseType: T | undefined
|
|
584
|
+
): Promise<typeof responseType extends undefined ? Document : InstanceType<T>>;
|
|
585
|
+
|
|
586
|
+
public async command(
|
|
587
|
+
ns: MongoDBNamespace,
|
|
588
|
+
command: Document,
|
|
589
|
+
options?: CommandOptions
|
|
590
|
+
): Promise<Document>;
|
|
591
|
+
|
|
558
592
|
public async command(
|
|
559
593
|
ns: MongoDBNamespace,
|
|
560
594
|
command: Document,
|
|
561
|
-
options: CommandOptions = {}
|
|
595
|
+
options: CommandOptions = {},
|
|
596
|
+
responseType?: MongoDBResponseConstructor
|
|
562
597
|
): Promise<Document> {
|
|
563
598
|
this.throwIfAborted();
|
|
564
|
-
for await (const document of this.sendCommand(ns, command, options)) {
|
|
599
|
+
for await (const document of this.sendCommand(ns, command, options, responseType)) {
|
|
565
600
|
return document;
|
|
566
601
|
}
|
|
567
602
|
throw new MongoUnexpectedServerResponseError('Unable to get response from server');
|
|
@@ -622,7 +657,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
622
657
|
*
|
|
623
658
|
* Note that `for-await` loops call `return` automatically when the loop is exited.
|
|
624
659
|
*/
|
|
625
|
-
private async *readMany(): AsyncGenerator<OpMsgResponse |
|
|
660
|
+
private async *readMany(): AsyncGenerator<OpMsgResponse | OpReply> {
|
|
626
661
|
try {
|
|
627
662
|
this.dataEvents = onData(this.messageStream);
|
|
628
663
|
for await (const message of this.dataEvents) {
|
|
@@ -687,11 +722,24 @@ export class CryptoConnection extends Connection {
|
|
|
687
722
|
this.autoEncrypter = options.autoEncrypter;
|
|
688
723
|
}
|
|
689
724
|
|
|
690
|
-
|
|
691
|
-
|
|
725
|
+
public override async command<T extends MongoDBResponseConstructor>(
|
|
726
|
+
ns: MongoDBNamespace,
|
|
727
|
+
command: Document,
|
|
728
|
+
options: CommandOptions | undefined,
|
|
729
|
+
responseType: T
|
|
730
|
+
): Promise<InstanceType<T>>;
|
|
731
|
+
|
|
732
|
+
public override async command(
|
|
733
|
+
ns: MongoDBNamespace,
|
|
734
|
+
command: Document,
|
|
735
|
+
options?: CommandOptions
|
|
736
|
+
): Promise<Document>;
|
|
737
|
+
|
|
738
|
+
override async command<T extends MongoDBResponseConstructor>(
|
|
692
739
|
ns: MongoDBNamespace,
|
|
693
740
|
cmd: Document,
|
|
694
|
-
options
|
|
741
|
+
options?: CommandOptions,
|
|
742
|
+
responseType?: T | undefined
|
|
695
743
|
): Promise<Document> {
|
|
696
744
|
const { autoEncrypter } = this;
|
|
697
745
|
if (!autoEncrypter) {
|
|
@@ -705,7 +753,7 @@ export class CryptoConnection extends Connection {
|
|
|
705
753
|
const serverWireVersion = maxWireVersion(this);
|
|
706
754
|
if (serverWireVersion === 0) {
|
|
707
755
|
// This means the initial handshake hasn't happened yet
|
|
708
|
-
return await super.command(ns, cmd, options);
|
|
756
|
+
return await super.command<T>(ns, cmd, options, responseType);
|
|
709
757
|
}
|
|
710
758
|
|
|
711
759
|
if (serverWireVersion < 8) {
|
|
@@ -739,7 +787,7 @@ export class CryptoConnection extends Connection {
|
|
|
739
787
|
}
|
|
740
788
|
}
|
|
741
789
|
|
|
742
|
-
const response = await super.command(ns, encrypted, options);
|
|
790
|
+
const response = await super.command<T>(ns, encrypted, options, responseType);
|
|
743
791
|
|
|
744
792
|
return await autoEncrypter.decrypt(response, options);
|
|
745
793
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type MessageHeader,
|
|
9
9
|
OpCompressedRequest,
|
|
10
10
|
OpMsgResponse,
|
|
11
|
-
|
|
11
|
+
OpReply,
|
|
12
12
|
type WriteProtocolMessageType
|
|
13
13
|
} from '../commands';
|
|
14
14
|
import { OP_COMPRESSED, OP_MSG } from './constants';
|
|
@@ -163,9 +163,7 @@ export async function compressCommand(
|
|
|
163
163
|
*
|
|
164
164
|
* This method does not parse the response's BSON.
|
|
165
165
|
*/
|
|
166
|
-
export async function decompressResponse(
|
|
167
|
-
message: Buffer
|
|
168
|
-
): Promise<OpMsgResponse | OpQueryResponse> {
|
|
166
|
+
export async function decompressResponse(message: Buffer): Promise<OpMsgResponse | OpReply> {
|
|
169
167
|
const messageHeader: MessageHeader = {
|
|
170
168
|
length: message.readInt32LE(0),
|
|
171
169
|
requestId: message.readInt32LE(4),
|
|
@@ -174,7 +172,7 @@ export async function decompressResponse(
|
|
|
174
172
|
};
|
|
175
173
|
|
|
176
174
|
if (messageHeader.opCode !== OP_COMPRESSED) {
|
|
177
|
-
const ResponseType = messageHeader.opCode === OP_MSG ? OpMsgResponse :
|
|
175
|
+
const ResponseType = messageHeader.opCode === OP_MSG ? OpMsgResponse : OpReply;
|
|
178
176
|
const messageBody = message.subarray(MESSAGE_HEADER_SIZE);
|
|
179
177
|
return new ResponseType(message, messageHeader, messageBody);
|
|
180
178
|
}
|
|
@@ -189,7 +187,7 @@ export async function decompressResponse(
|
|
|
189
187
|
const compressedBuffer = message.slice(MESSAGE_HEADER_SIZE + 9);
|
|
190
188
|
|
|
191
189
|
// recalculate based on wrapped opcode
|
|
192
|
-
const ResponseType = header.opCode === OP_MSG ? OpMsgResponse :
|
|
190
|
+
const ResponseType = header.opCode === OP_MSG ? OpMsgResponse : OpReply;
|
|
193
191
|
const messageBody = await decompress(compressorID, compressedBuffer);
|
|
194
192
|
if (messageBody.length !== header.length) {
|
|
195
193
|
throw new MongoDecompressionError('Message body and message header must be the same length');
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type BSONSerializeOptions, BSONType, type Document, type Timestamp } from '../../bson';
|
|
2
|
+
import { type ClusterTime } from '../../sdam/common';
|
|
3
|
+
import { OnDemandDocument } from './on_demand/document';
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
export type MongoDBResponseConstructor = {
|
|
7
|
+
new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export class MongoDBResponse extends OnDemandDocument {
|
|
12
|
+
// {ok:1}
|
|
13
|
+
static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
|
|
14
|
+
|
|
15
|
+
/** Indicates this document is a server error */
|
|
16
|
+
public get isError() {
|
|
17
|
+
let isError = this.ok === 0;
|
|
18
|
+
isError ||= this.has('errmsg');
|
|
19
|
+
isError ||= this.has('code');
|
|
20
|
+
isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
|
|
21
|
+
return isError;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the
|
|
26
|
+
* contents of the document.
|
|
27
|
+
*/
|
|
28
|
+
get recoveryToken(): Document | null {
|
|
29
|
+
return (
|
|
30
|
+
this.get('recoveryToken', BSONType.object)?.toObject({
|
|
31
|
+
promoteValues: false,
|
|
32
|
+
promoteLongs: false,
|
|
33
|
+
promoteBuffers: false
|
|
34
|
+
}) ?? null
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The server creates a cursor in response to a snapshot find/aggregate command and reports atClusterTime within the cursor field in the response.
|
|
40
|
+
* For the distinct command the server adds a top-level atClusterTime field to the response.
|
|
41
|
+
* The atClusterTime field represents the timestamp of the read and is guaranteed to be majority committed.
|
|
42
|
+
*/
|
|
43
|
+
public get atClusterTime(): Timestamp | null {
|
|
44
|
+
return (
|
|
45
|
+
this.get('cursor', BSONType.object)?.get('atClusterTime', BSONType.timestamp) ??
|
|
46
|
+
this.get('atClusterTime', BSONType.timestamp)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get operationTime(): Timestamp | null {
|
|
51
|
+
return this.get('operationTime', BSONType.timestamp);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public get ok(): 0 | 1 {
|
|
55
|
+
return this.getNumber('ok') ? 1 : 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public get $err(): string | null {
|
|
59
|
+
return this.get('$err', BSONType.string);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public get errmsg(): string | null {
|
|
63
|
+
return this.get('errmsg', BSONType.string);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public get code(): number | null {
|
|
67
|
+
return this.getNumber('code');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private clusterTime?: ClusterTime | null;
|
|
71
|
+
public get $clusterTime(): ClusterTime | null {
|
|
72
|
+
if (!('clusterTime' in this)) {
|
|
73
|
+
const clusterTimeDoc = this.get('$clusterTime', BSONType.object);
|
|
74
|
+
if (clusterTimeDoc == null) {
|
|
75
|
+
this.clusterTime = null;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const clusterTime = clusterTimeDoc.get('clusterTime', BSONType.timestamp, true);
|
|
79
|
+
const signature = clusterTimeDoc.get('signature', BSONType.object)?.toObject();
|
|
80
|
+
// @ts-expect-error: `signature` is incorrectly typed. It is public API.
|
|
81
|
+
this.clusterTime = { clusterTime, signature };
|
|
82
|
+
}
|
|
83
|
+
return this.clusterTime ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public override toObject(options: BSONSerializeOptions = {}): Record<string, any> {
|
|
87
|
+
const exactBSONOptions = {
|
|
88
|
+
useBigInt64: options.useBigInt64,
|
|
89
|
+
promoteLongs: options.promoteLongs,
|
|
90
|
+
promoteValues: options.promoteValues,
|
|
91
|
+
promoteBuffers: options.promoteBuffers,
|
|
92
|
+
bsonRegExp: options.bsonRegExp,
|
|
93
|
+
raw: options.raw ?? false,
|
|
94
|
+
fieldsAsRaw: options.fieldsAsRaw ?? {},
|
|
95
|
+
validation: this.parseBsonSerializationOptions(options)
|
|
96
|
+
};
|
|
97
|
+
return super.toObject(exactBSONOptions);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
|
|
101
|
+
utf8: { writeErrors: false } | false;
|
|
102
|
+
} {
|
|
103
|
+
if (enableUtf8Validation === false) {
|
|
104
|
+
return { utf8: false };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { utf8: { writeErrors: false } };
|
|
108
|
+
}
|
|
109
|
+
}
|