connection.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.CryptoConnection = exports.SizedMessageTransform = exports.Connection = void 0;
  4. exports.hasSessionSupport = hasSessionSupport;
  5. const stream_1 = require("stream");
  6. const timers_1 = require("timers");
  7. const bson_1 = require("../bson");
  8. const constants_1 = require("../constants");
  9. const error_1 = require("../error");
  10. const mongo_logger_1 = require("../mongo_logger");
  11. const mongo_types_1 = require("../mongo_types");
  12. const read_preference_1 = require("../read_preference");
  13. const common_1 = require("../sdam/common");
  14. const sessions_1 = require("../sessions");
  15. const timeout_1 = require("../timeout");
  16. const utils_1 = require("../utils");
  17. const command_monitoring_events_1 = require("./command_monitoring_events");
  18. const commands_1 = require("./commands");
  19. const stream_description_1 = require("./stream_description");
  20. const compression_1 = require("./wire_protocol/compression");
  21. const on_data_1 = require("./wire_protocol/on_data");
  22. const responses_1 = require("./wire_protocol/responses");
  23. const shared_1 = require("./wire_protocol/shared");
  24. /** @internal */
  25. function hasSessionSupport(conn) {
  26. const description = conn.description;
  27. return description.logicalSessionTimeoutMinutes != null;
  28. }
  29. function streamIdentifier(stream, options) {
  30. if (options.proxyHost) {
  31. // If proxy options are specified, the properties of `stream` itself
  32. // will not accurately reflect what endpoint this is connected to.
  33. return options.hostAddress.toString();
  34. }
  35. const { remoteAddress, remotePort } = stream;
  36. if (typeof remoteAddress === 'string' && typeof remotePort === 'number') {
  37. return utils_1.HostAddress.fromHostPort(remoteAddress, remotePort).toString();
  38. }
  39. return (0, utils_1.uuidV4)().toString('hex');
  40. }
  41. /** @internal */
  42. class Connection extends mongo_types_1.TypedEventEmitter {
  43. constructor(stream, options) {
  44. super();
  45. this.lastHelloMS = -1;
  46. this.helloOk = false;
  47. this.delayedTimeoutId = null;
  48. /** Indicates that the connection (including underlying TCP socket) has been closed. */
  49. this.closed = false;
  50. this.clusterTime = null;
  51. this.error = null;
  52. this.dataEvents = null;
  53. this.on('error', utils_1.noop);
  54. this.socket = stream;
  55. this.id = options.id;
  56. this.address = streamIdentifier(stream, options);
  57. this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
  58. this.monitorCommands = options.monitorCommands;
  59. this.serverApi = options.serverApi;
  60. this.mongoLogger = options.mongoLogger;
  61. this.established = false;
  62. this.description = new stream_description_1.StreamDescription(this.address, options);
  63. this.generation = options.generation;
  64. this.lastUseTime = (0, utils_1.now)();
  65. this.messageStream = this.socket
  66. .on('error', this.onSocketError.bind(this))
  67. .pipe(new SizedMessageTransform({ connection: this }))
  68. .on('error', this.onTransformError.bind(this));
  69. this.socket.on('close', this.onClose.bind(this));
  70. this.socket.on('timeout', this.onTimeout.bind(this));
  71. this.messageStream.pause();
  72. }
  73. get hello() {
  74. return this.description.hello;
  75. }
  76. // the `connect` method stores the result of the handshake hello on the connection
  77. set hello(response) {
  78. this.description.receiveResponse(response);
  79. Object.freeze(this.description);
  80. }
  81. get serviceId() {
  82. return this.hello?.serviceId;
  83. }
  84. get loadBalanced() {
  85. return this.description.loadBalanced;
  86. }
  87. get idleTime() {
  88. return (0, utils_1.calculateDurationInMs)(this.lastUseTime);
  89. }
  90. get hasSessionSupport() {
  91. return this.description.logicalSessionTimeoutMinutes != null;
  92. }
  93. get supportsOpMsg() {
  94. return (this.description != null &&
  95. // TODO(NODE-6672,NODE-6287): This guard is primarily for maxWireVersion = 0
  96. (0, utils_1.maxWireVersion)(this) >= 6 &&
  97. !this.description.__nodejs_mock_server__);
  98. }
  99. get shouldEmitAndLogCommand() {
  100. return ((this.monitorCommands ||
  101. (this.established &&
  102. !this.authContext?.reauthenticating &&
  103. this.mongoLogger?.willLog(mongo_logger_1.MongoLoggableComponent.COMMAND, mongo_logger_1.SeverityLevel.DEBUG))) ??
  104. false);
  105. }
  106. markAvailable() {
  107. this.lastUseTime = (0, utils_1.now)();
  108. }
  109. onSocketError(cause) {
  110. this.onError(new error_1.MongoNetworkError(cause.message, { cause }));
  111. }
  112. onTransformError(error) {
  113. this.onError(error);
  114. }
  115. onError(error) {
  116. this.cleanup(error);
  117. }
  118. onClose() {
  119. const message = `connection ${this.id} to ${this.address} closed`;
  120. this.cleanup(new error_1.MongoNetworkError(message));
  121. }
  122. onTimeout() {
  123. this.delayedTimeoutId = (0, timers_1.setTimeout)(() => {
  124. const message = `connection ${this.id} to ${this.address} timed out`;
  125. const beforeHandshake = this.hello == null;
  126. this.cleanup(new error_1.MongoNetworkTimeoutError(message, { beforeHandshake }));
  127. }, 1).unref(); // No need for this timer to hold the event loop open
  128. }
  129. destroy() {
  130. if (this.closed) {
  131. return;
  132. }
  133. // load balanced mode requires that these listeners remain on the connection
  134. // after cleanup on timeouts, errors or close so we remove them before calling
  135. // cleanup.
  136. this.removeAllListeners(Connection.PINNED);
  137. this.removeAllListeners(Connection.UNPINNED);
  138. const message = `connection ${this.id} to ${this.address} closed`;
  139. this.cleanup(new error_1.MongoNetworkError(message));
  140. }
  141. /**
  142. * A method that cleans up the connection. When `force` is true, this method
  143. * forcibly destroys the socket.
  144. *
  145. * If an error is provided, any in-flight operations will be closed with the error.
  146. *
  147. * This method does nothing if the connection is already closed.
  148. */
  149. cleanup(error) {
  150. if (this.closed) {
  151. return;
  152. }
  153. this.socket.destroy();
  154. this.error = error;
  155. this.dataEvents?.throw(error).then(undefined, utils_1.squashError);
  156. this.closed = true;
  157. this.emit(Connection.CLOSE);
  158. }
  159. prepareCommand(db, command, options) {
  160. let cmd = { ...command };
  161. const readPreference = (0, shared_1.getReadPreference)(options);
  162. const session = options?.session;
  163. let clusterTime = this.clusterTime;
  164. if (this.serverApi) {
  165. const { version, strict, deprecationErrors } = this.serverApi;
  166. cmd.apiVersion = version;
  167. if (strict != null)
  168. cmd.apiStrict = strict;
  169. if (deprecationErrors != null)
  170. cmd.apiDeprecationErrors = deprecationErrors;
  171. }
  172. if (this.hasSessionSupport && session) {
  173. if (session.clusterTime &&
  174. clusterTime &&
  175. session.clusterTime.clusterTime.greaterThan(clusterTime.clusterTime)) {
  176. clusterTime = session.clusterTime;
  177. }
  178. const sessionError = (0, sessions_1.applySession)(session, cmd, options);
  179. if (sessionError)
  180. throw sessionError;
  181. }
  182. else if (session?.explicit) {
  183. throw new error_1.MongoCompatibilityError('Current topology does not support sessions');
  184. }
  185. // if we have a known cluster time, gossip it
  186. if (clusterTime) {
  187. cmd.$clusterTime = clusterTime;
  188. }
  189. // For standalone, drivers MUST NOT set $readPreference.
  190. if (this.description.type !== common_1.ServerType.Standalone) {
  191. if (!(0, shared_1.isSharded)(this) &&
  192. !this.description.loadBalanced &&
  193. this.supportsOpMsg &&
  194. options.directConnection === true &&
  195. readPreference?.mode === 'primary') {
  196. // For mongos and load balancers with 'primary' mode, drivers MUST NOT set $readPreference.
  197. // For all other types with a direct connection, if the read preference is 'primary'
  198. // (driver sets 'primary' as default if no read preference is configured),
  199. // the $readPreference MUST be set to 'primaryPreferred'
  200. // to ensure that any server type can handle the request.
  201. cmd.$readPreference = read_preference_1.ReadPreference.primaryPreferred.toJSON();
  202. }
  203. else if ((0, shared_1.isSharded)(this) && !this.supportsOpMsg && readPreference?.mode !== 'primary') {
  204. // When sending a read operation via OP_QUERY and the $readPreference modifier,
  205. // the query MUST be provided using the $query modifier.
  206. cmd = {
  207. $query: cmd,
  208. $readPreference: readPreference.toJSON()
  209. };
  210. }
  211. else if (readPreference?.mode !== 'primary') {
  212. // For mode 'primary', drivers MUST NOT set $readPreference.
  213. // For all other read preference modes (i.e. 'secondary', 'primaryPreferred', ...),
  214. // drivers MUST set $readPreference
  215. cmd.$readPreference = readPreference.toJSON();
  216. }
  217. }
  218. const commandOptions = {
  219. numberToSkip: 0,
  220. numberToReturn: -1,
  221. checkKeys: false,
  222. // This value is not overridable
  223. secondaryOk: readPreference.secondaryOk(),
  224. ...options
  225. };
  226. options.timeoutContext?.addMaxTimeMSToCommand(cmd, options);
  227. const message = this.supportsOpMsg
  228. ? new commands_1.OpMsgRequest(db, cmd, commandOptions)
  229. : new commands_1.OpQueryRequest(db, cmd, commandOptions);
  230. return message;
  231. }
  232. async *sendWire(message, options, responseType) {
  233. this.throwIfAborted();
  234. const timeout = options.socketTimeoutMS ??
  235. options?.timeoutContext?.getSocketTimeoutMS() ??
  236. this.socketTimeoutMS;
  237. this.socket.setTimeout(timeout);
  238. try {
  239. await this.writeCommand(message, {
  240. agreedCompressor: this.description.compressor ?? 'none',
  241. zlibCompressionLevel: this.description.zlibCompressionLevel,
  242. timeoutContext: options.timeoutContext,
  243. signal: options.signal
  244. });
  245. if (options.noResponse || message.moreToCome) {
  246. yield responses_1.MongoDBResponse.empty;
  247. return;
  248. }
  249. this.throwIfAborted();
  250. if (options.timeoutContext?.csotEnabled() &&
  251. options.timeoutContext.minRoundTripTime != null &&
  252. options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime) {
  253. throw new error_1.MongoOperationTimeoutError('Server roundtrip time is greater than the time remaining');
  254. }
  255. for await (const response of this.readMany(options)) {
  256. this.socket.setTimeout(0);
  257. const bson = response.parse();
  258. const document = (responseType ?? responses_1.MongoDBResponse).make(bson);
  259. yield document;
  260. this.throwIfAborted();
  261. this.socket.setTimeout(timeout);
  262. }
  263. }
  264. finally {
  265. this.socket.setTimeout(0);
  266. }
  267. }
  268. async *sendCommand(ns, command, options, responseType) {
  269. options?.signal?.throwIfAborted();
  270. const message = this.prepareCommand(ns.db, command, options);
  271. let started = 0;
  272. if (this.shouldEmitAndLogCommand) {
  273. started = (0, utils_1.now)();
  274. this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_STARTED, message.databaseName, this.established, new command_monitoring_events_1.CommandStartedEvent(this, message, this.description.serverConnectionId));
  275. }
  276. // If `documentsReturnedIn` not set or raw is not enabled, use input bson options
  277. // Otherwise, support raw flag. Raw only works for cursors that hardcode firstBatch/nextBatch fields
  278. const bsonOptions = options.documentsReturnedIn == null || !options.raw
  279. ? options
  280. : {
  281. ...options,
  282. raw: false,
  283. fieldsAsRaw: { [options.documentsReturnedIn]: true }
  284. };
  285. /** MongoDBResponse instance or subclass */
  286. let document = undefined;
  287. /** Cached result of a toObject call */
  288. let object = undefined;
  289. try {
  290. this.throwIfAborted();
  291. for await (document of this.sendWire(message, options, responseType)) {
  292. object = undefined;
  293. if (options.session != null) {
  294. (0, sessions_1.updateSessionFromResponse)(options.session, document);
  295. }
  296. if (document.$clusterTime) {
  297. this.clusterTime = document.$clusterTime;
  298. this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
  299. }
  300. if (document.ok === 0) {
  301. if (options.timeoutContext?.csotEnabled() && document.isMaxTimeExpiredError) {
  302. throw new error_1.MongoOperationTimeoutError('Server reported a timeout error', {
  303. cause: new error_1.MongoServerError((object ??= document.toObject(bsonOptions)))
  304. });
  305. }
  306. throw new error_1.MongoServerError((object ??= document.toObject(bsonOptions)));
  307. }
  308. if (this.shouldEmitAndLogCommand) {
  309. this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_SUCCEEDED, message.databaseName, this.established, new command_monitoring_events_1.CommandSucceededEvent(this, message, options.noResponse
  310. ? undefined
  311. : message.moreToCome
  312. ? { ok: 1 }
  313. : (object ??= document.toObject(bsonOptions)), started, this.description.serverConnectionId));
  314. }
  315. if (responseType == null) {
  316. yield (object ??= document.toObject(bsonOptions));
  317. }
  318. else {
  319. yield document;
  320. }
  321. this.throwIfAborted();
  322. }
  323. }
  324. catch (error) {
  325. if (this.shouldEmitAndLogCommand) {
  326. this.emitAndLogCommand(this.monitorCommands, Connection.COMMAND_FAILED, message.databaseName, this.established, new command_monitoring_events_1.CommandFailedEvent(this, message, error, started, this.description.serverConnectionId));
  327. }
  328. throw error;
  329. }
  330. }
  331. async command(ns, command, options = {}, responseType) {
  332. this.throwIfAborted();
  333. options.signal?.throwIfAborted();
  334. for await (const document of this.sendCommand(ns, command, options, responseType)) {
  335. if (options.timeoutContext?.csotEnabled()) {
  336. if (responses_1.MongoDBResponse.is(document)) {
  337. if (document.isMaxTimeExpiredError) {
  338. throw new error_1.MongoOperationTimeoutError('Server reported a timeout error', {
  339. cause: new error_1.MongoServerError(document.toObject())
  340. });
  341. }
  342. }
  343. else {
  344. if ((Array.isArray(document?.writeErrors) &&
  345. document.writeErrors.some(error => error?.code === error_1.MONGODB_ERROR_CODES.MaxTimeMSExpired)) ||
  346. document?.writeConcernError?.code === error_1.MONGODB_ERROR_CODES.MaxTimeMSExpired) {
  347. throw new error_1.MongoOperationTimeoutError('Server reported a timeout error', {
  348. cause: new error_1.MongoServerError(document)
  349. });
  350. }
  351. }
  352. }
  353. return document;
  354. }
  355. throw new error_1.MongoUnexpectedServerResponseError('Unable to get response from server');
  356. }
  357. exhaustCommand(ns, command, options, replyListener) {
  358. const exhaustLoop = async () => {
  359. this.throwIfAborted();
  360. for await (const reply of this.sendCommand(ns, command, options)) {
  361. replyListener(undefined, reply);
  362. this.throwIfAborted();
  363. }
  364. throw new error_1.MongoUnexpectedServerResponseError('Server ended moreToCome unexpectedly');
  365. };
  366. exhaustLoop().then(undefined, replyListener);
  367. }
  368. throwIfAborted() {
  369. if (this.error)
  370. throw this.error;
  371. }
  372. /**
  373. * @internal
  374. *
  375. * Writes an OP_MSG or OP_QUERY request to the socket, optionally compressing the command. This method
  376. * waits until the socket's buffer has emptied (the Nodejs socket `drain` event has fired).
  377. */
  378. async writeCommand(command, options) {
  379. const finalCommand = options.agreedCompressor === 'none' || !commands_1.OpCompressedRequest.canCompress(command)
  380. ? command
  381. : new commands_1.OpCompressedRequest(command, {
  382. agreedCompressor: options.agreedCompressor ?? 'none',
  383. zlibCompressionLevel: options.zlibCompressionLevel ?? 0
  384. });
  385. const buffer = Buffer.concat(await finalCommand.toBin());
  386. if (options.timeoutContext?.csotEnabled()) {
  387. if (options.timeoutContext.minRoundTripTime != null &&
  388. options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime) {
  389. throw new error_1.MongoOperationTimeoutError('Server roundtrip time is greater than the time remaining');
  390. }
  391. }
  392. if (this.socket.write(buffer))
  393. return;
  394. const drainEvent = (0, utils_1.once)(this.socket, 'drain', options);
  395. const timeout = options?.timeoutContext?.timeoutForSocketWrite;
  396. const drained = timeout ? Promise.race([drainEvent, timeout]) : drainEvent;
  397. try {
  398. return await drained;
  399. }
  400. catch (writeError) {
  401. if (timeout_1.TimeoutError.is(writeError)) {
  402. const timeoutError = new error_1.MongoOperationTimeoutError('Timed out at socket write');
  403. this.onError(timeoutError);
  404. throw timeoutError;
  405. }
  406. else if (writeError === options.signal?.reason) {
  407. this.onError(writeError);
  408. }
  409. throw writeError;
  410. }
  411. finally {
  412. timeout?.clear();
  413. }
  414. }
  415. /**
  416. * @internal
  417. *
  418. * Returns an async generator that yields full wire protocol messages from the underlying socket. This function
  419. * yields messages until `moreToCome` is false or not present in a response, or the caller cancels the request
  420. * by calling `return` on the generator.
  421. *
  422. * Note that `for-await` loops call `return` automatically when the loop is exited.
  423. */
  424. async *readMany(options) {
  425. try {
  426. this.dataEvents = (0, on_data_1.onData)(this.messageStream, options);
  427. this.messageStream.resume();
  428. for await (const message of this.dataEvents) {
  429. const response = await (0, compression_1.decompressResponse)(message);
  430. yield response;
  431. if (!response.moreToCome) {
  432. return;
  433. }
  434. }
  435. }
  436. catch (readError) {
  437. if (timeout_1.TimeoutError.is(readError)) {
  438. const timeoutError = new error_1.MongoOperationTimeoutError(`Timed out during socket read (${readError.duration}ms)`);
  439. this.dataEvents = null;
  440. this.onError(timeoutError);
  441. throw timeoutError;
  442. }
  443. else if (readError === options.signal?.reason) {
  444. this.onError(readError);
  445. }
  446. throw readError;
  447. }
  448. finally {
  449. this.dataEvents = null;
  450. this.messageStream.pause();
  451. }
  452. }
  453. }
  454. exports.Connection = Connection;
  455. /** @event */
  456. Connection.COMMAND_STARTED = constants_1.COMMAND_STARTED;
  457. /** @event */
  458. Connection.COMMAND_SUCCEEDED = constants_1.COMMAND_SUCCEEDED;
  459. /** @event */
  460. Connection.COMMAND_FAILED = constants_1.COMMAND_FAILED;
  461. /** @event */
  462. Connection.CLUSTER_TIME_RECEIVED = constants_1.CLUSTER_TIME_RECEIVED;
  463. /** @event */
  464. Connection.CLOSE = constants_1.CLOSE;
  465. /** @event */
  466. Connection.PINNED = constants_1.PINNED;
  467. /** @event */
  468. Connection.UNPINNED = constants_1.UNPINNED;
  469. /** @internal */
  470. class SizedMessageTransform extends stream_1.Transform {
  471. constructor({ connection }) {
  472. super({ writableObjectMode: false, readableObjectMode: true });
  473. this.bufferPool = new utils_1.BufferPool();
  474. this.connection = connection;
  475. }
  476. _transform(chunk, encoding, callback) {
  477. if (this.connection.delayedTimeoutId != null) {
  478. (0, timers_1.clearTimeout)(this.connection.delayedTimeoutId);
  479. this.connection.delayedTimeoutId = null;
  480. }
  481. this.bufferPool.append(chunk);
  482. while (this.bufferPool.length) {
  483. // While there are any bytes in the buffer
  484. // Try to fetch a size from the top 4 bytes
  485. const sizeOfMessage = this.bufferPool.getInt32();
  486. if (sizeOfMessage == null) {
  487. // Not even an int32 worth of data. Stop the loop, we need more chunks.
  488. break;
  489. }
  490. if (sizeOfMessage < 0) {
  491. // The size in the message has a negative value, this is probably corruption, throw:
  492. return callback(new error_1.MongoParseError(`Message size cannot be negative: ${sizeOfMessage}`));
  493. }
  494. if (sizeOfMessage > this.bufferPool.length) {
  495. // We do not have enough bytes to make a sizeOfMessage chunk
  496. break;
  497. }
  498. // Add a message to the stream
  499. const message = this.bufferPool.read(sizeOfMessage);
  500. if (!this.push(message)) {
  501. // We only subscribe to data events so we should never get backpressure
  502. // if we do, we do not have the handling for it.
  503. return callback(new error_1.MongoRuntimeError(`SizedMessageTransform does not support backpressure`));
  504. }
  505. }
  506. callback();
  507. }
  508. }
  509. exports.SizedMessageTransform = SizedMessageTransform;
  510. /** @internal */
  511. class CryptoConnection extends Connection {
  512. constructor(stream, options) {
  513. super(stream, options);
  514. this.autoEncrypter = options.autoEncrypter;
  515. }
  516. async command(ns, cmd, options, responseType) {
  517. const { autoEncrypter } = this;
  518. if (!autoEncrypter) {
  519. // TODO(NODE-6065): throw a MongoRuntimeError in Node V7
  520. // @ts-expect-error No cause provided because there is no underlying error.
  521. throw new error_1.MongoMissingDependencyError('No AutoEncrypter available for encryption', {
  522. dependencyName: 'n/a'
  523. });
  524. }
  525. const serverWireVersion = (0, utils_1.maxWireVersion)(this);
  526. if (serverWireVersion === 0) {
  527. // This means the initial handshake hasn't happened yet
  528. return await super.command(ns, cmd, options, responseType);
  529. }
  530. // Save sort or indexKeys based on the command being run
  531. // the encrypt API serializes our JS objects to BSON to pass to the native code layer
  532. // and then deserializes the encrypted result, the protocol level components
  533. // of the command (ex. sort) are then converted to JS objects potentially losing
  534. // import key order information. These fields are never encrypted so we can save the values
  535. // from before the encryption and replace them after encryption has been performed
  536. const sort = cmd.find || cmd.findAndModify ? cmd.sort : null;
  537. const indexKeys = cmd.createIndexes
  538. ? cmd.indexes.map((index) => index.key)
  539. : null;
  540. const encrypted = await autoEncrypter.encrypt(ns.toString(), cmd, options);
  541. // Replace the saved values
  542. if (sort != null && (cmd.find || cmd.findAndModify)) {
  543. encrypted.sort = sort;
  544. }
  545. if (indexKeys != null && cmd.createIndexes) {
  546. for (const [offset, index] of indexKeys.entries()) {
  547. // @ts-expect-error `encrypted` is a generic "command", but we've narrowed for only `createIndexes` commands here
  548. encrypted.indexes[offset].key = index;
  549. }
  550. }
  551. const encryptedResponse = await super.command(ns, encrypted, options,
  552. // Eventually we want to require `responseType` which means we would satisfy `T` as the return type.
  553. // In the meantime, we want encryptedResponse to always be _at least_ a MongoDBResponse if not a more specific subclass
  554. // So that we can ensure we have access to the on-demand APIs for decorate response
  555. responseType ?? responses_1.MongoDBResponse);
  556. const result = await autoEncrypter.decrypt(encryptedResponse.toBytes(), options);
  557. const decryptedResponse = responseType?.make(result) ?? (0, bson_1.deserialize)(result, options);
  558. if (autoEncrypter[constants_1.kDecorateResult]) {
  559. if (responseType == null) {
  560. (0, utils_1.decorateDecryptionResult)(decryptedResponse, encryptedResponse.toObject(), true);
  561. }
  562. else if (decryptedResponse instanceof responses_1.CursorResponse) {
  563. decryptedResponse.encryptedResponse = encryptedResponse;
  564. }
  565. }
  566. return decryptedResponse;
  567. }
  568. }
  569. exports.CryptoConnection = CryptoConnection;
  570. //# sourceMappingURL=connection.js.map