mongodb 6.7.0-dev.20240613.sha.c1af6adc → 6.7.0-dev.20240615.sha.465ffd97

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.
Files changed (96) hide show
  1. package/README.md +3 -3
  2. package/lib/bson.js.map +1 -1
  3. package/lib/client-side-encryption/auto_encrypter.js +8 -61
  4. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  5. package/lib/client-side-encryption/client_encryption.js +5 -5
  6. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  7. package/lib/client-side-encryption/providers/index.js.map +1 -1
  8. package/lib/client-side-encryption/state_machine.js +15 -11
  9. package/lib/client-side-encryption/state_machine.js.map +1 -1
  10. package/lib/cmap/connection.js +22 -20
  11. package/lib/cmap/connection.js.map +1 -1
  12. package/lib/cmap/wire_protocol/on_demand/document.js +8 -5
  13. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  14. package/lib/cmap/wire_protocol/responses.js +116 -40
  15. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  16. package/lib/constants.js +9 -1
  17. package/lib/constants.js.map +1 -1
  18. package/lib/cursor/abstract_cursor.js +24 -60
  19. package/lib/cursor/abstract_cursor.js.map +1 -1
  20. package/lib/cursor/aggregation_cursor.js +2 -3
  21. package/lib/cursor/aggregation_cursor.js.map +1 -1
  22. package/lib/cursor/change_stream_cursor.js +6 -8
  23. package/lib/cursor/change_stream_cursor.js.map +1 -1
  24. package/lib/cursor/find_cursor.js +5 -17
  25. package/lib/cursor/find_cursor.js.map +1 -1
  26. package/lib/cursor/list_collections_cursor.js +0 -1
  27. package/lib/cursor/list_collections_cursor.js.map +1 -1
  28. package/lib/cursor/list_indexes_cursor.js +0 -1
  29. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  30. package/lib/cursor/run_command_cursor.js +4 -6
  31. package/lib/cursor/run_command_cursor.js.map +1 -1
  32. package/lib/error.js +6 -21
  33. package/lib/error.js.map +1 -1
  34. package/lib/operations/aggregate.js +2 -2
  35. package/lib/operations/aggregate.js.map +1 -1
  36. package/lib/operations/bulk_write.js +1 -2
  37. package/lib/operations/bulk_write.js.map +1 -1
  38. package/lib/operations/command.js +2 -3
  39. package/lib/operations/command.js.map +1 -1
  40. package/lib/operations/count_documents.js +1 -7
  41. package/lib/operations/count_documents.js.map +1 -1
  42. package/lib/operations/execute_operation.js.map +1 -1
  43. package/lib/operations/find.js +2 -1
  44. package/lib/operations/find.js.map +1 -1
  45. package/lib/operations/get_more.js +1 -1
  46. package/lib/operations/get_more.js.map +1 -1
  47. package/lib/operations/indexes.js +2 -1
  48. package/lib/operations/indexes.js.map +1 -1
  49. package/lib/operations/list_collections.js +2 -1
  50. package/lib/operations/list_collections.js.map +1 -1
  51. package/lib/operations/run_command.js +1 -1
  52. package/lib/operations/run_command.js.map +1 -1
  53. package/lib/operations/update.js +2 -1
  54. package/lib/operations/update.js.map +1 -1
  55. package/lib/sdam/server.js +7 -2
  56. package/lib/sdam/server.js.map +1 -1
  57. package/lib/utils.js +45 -1
  58. package/lib/utils.js.map +1 -1
  59. package/lib/write_concern.js +17 -1
  60. package/lib/write_concern.js.map +1 -1
  61. package/mongodb.d.ts +23 -10
  62. package/package.json +1 -1
  63. package/src/bson.ts +1 -0
  64. package/src/client-side-encryption/auto_encrypter.ts +9 -70
  65. package/src/client-side-encryption/client_encryption.ts +6 -6
  66. package/src/client-side-encryption/providers/index.ts +6 -9
  67. package/src/client-side-encryption/state_machine.ts +18 -16
  68. package/src/cmap/connection.ts +46 -50
  69. package/src/cmap/wire_protocol/on_demand/document.ts +13 -6
  70. package/src/cmap/wire_protocol/responses.ts +140 -45
  71. package/src/constants.ts +9 -0
  72. package/src/cursor/abstract_cursor.ts +51 -71
  73. package/src/cursor/aggregation_cursor.ts +13 -12
  74. package/src/cursor/change_stream_cursor.ts +20 -34
  75. package/src/cursor/find_cursor.ts +17 -25
  76. package/src/cursor/list_collections_cursor.ts +3 -4
  77. package/src/cursor/list_indexes_cursor.ts +3 -4
  78. package/src/cursor/run_command_cursor.ts +13 -19
  79. package/src/error.ts +16 -28
  80. package/src/index.ts +6 -7
  81. package/src/operations/aggregate.ts +12 -5
  82. package/src/operations/bulk_write.ts +1 -2
  83. package/src/operations/command.ts +17 -3
  84. package/src/operations/count_documents.ts +7 -11
  85. package/src/operations/delete.ts +2 -2
  86. package/src/operations/execute_operation.ts +0 -13
  87. package/src/operations/find.ts +7 -3
  88. package/src/operations/find_and_modify.ts +1 -1
  89. package/src/operations/get_more.ts +6 -10
  90. package/src/operations/indexes.ts +7 -3
  91. package/src/operations/list_collections.ts +8 -3
  92. package/src/operations/run_command.ts +16 -6
  93. package/src/operations/update.ts +2 -1
  94. package/src/sdam/server.ts +7 -2
  95. package/src/utils.ts +52 -2
  96. package/src/write_concern.ts +18 -0
@@ -6,6 +6,7 @@ import {
6
6
 
7
7
  import { deserialize, type Document, serialize } from '../bson';
8
8
  import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
9
+ import { kDecorateResult } from '../constants';
9
10
  import { getMongoDBClientEncryption } from '../deps';
10
11
  import { MongoRuntimeError } from '../error';
11
12
  import { MongoClient, type MongoClientOptions } from '../mongo_client';
@@ -212,15 +213,6 @@ export const AutoEncryptionLoggerLevel = Object.freeze({
212
213
  export type AutoEncryptionLoggerLevel =
213
214
  (typeof AutoEncryptionLoggerLevel)[keyof typeof AutoEncryptionLoggerLevel];
214
215
 
215
- // Typescript errors if we index objects with `Symbol.for(...)`, so
216
- // to avoid TS errors we pull them out into variables. Then we can type
217
- // the objects (and class) that we expect to see them on and prevent TS
218
- // errors.
219
- /** @internal */
220
- const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
221
- /** @internal */
222
- const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
223
-
224
216
  /**
225
217
  * @internal An internal class to be used by the driver for auto encryption
226
218
  * **NOTE**: Not meant to be instantiated directly, this is for internal use only.
@@ -467,16 +459,18 @@ export class AutoEncrypter {
467
459
  proxyOptions: this._proxyOptions,
468
460
  tlsOptions: this._tlsOptions
469
461
  });
470
- return await stateMachine.execute<Document>(this, context);
462
+
463
+ return deserialize(await stateMachine.execute(this, context), {
464
+ promoteValues: false,
465
+ promoteLongs: false
466
+ });
471
467
  }
472
468
 
473
469
  /**
474
470
  * Decrypt a command response
475
471
  */
476
- async decrypt(response: Uint8Array | Document, options: CommandOptions = {}): Promise<Document> {
477
- const buffer = Buffer.isBuffer(response) ? response : serialize(response, options);
478
-
479
- const context = this._mongocrypt.makeDecryptionContext(buffer);
472
+ async decrypt(response: Uint8Array, options: CommandOptions = {}): Promise<Uint8Array> {
473
+ const context = this._mongocrypt.makeDecryptionContext(response);
480
474
 
481
475
  context.id = this._contextCounter++;
482
476
 
@@ -486,12 +480,7 @@ export class AutoEncrypter {
486
480
  tlsOptions: this._tlsOptions
487
481
  });
488
482
 
489
- const decorateResult = this[kDecorateResult];
490
- const result = await stateMachine.execute<Document>(this, context);
491
- if (decorateResult) {
492
- decorateDecryptionResult(result, response);
493
- }
494
- return result;
483
+ return await stateMachine.execute(this, context);
495
484
  }
496
485
 
497
486
  /**
@@ -518,53 +507,3 @@ export class AutoEncrypter {
518
507
  return AutoEncrypter.getMongoCrypt().libmongocryptVersion;
519
508
  }
520
509
  }
521
-
522
- /**
523
- * Recurse through the (identically-shaped) `decrypted` and `original`
524
- * objects and attach a `decryptedKeys` property on each sub-object that
525
- * contained encrypted fields. Because we only call this on BSON responses,
526
- * we do not need to worry about circular references.
527
- *
528
- * @internal
529
- */
530
- function decorateDecryptionResult(
531
- decrypted: Document & { [kDecoratedKeys]?: Array<string> },
532
- original: Document,
533
- isTopLevelDecorateCall = true
534
- ): void {
535
- if (isTopLevelDecorateCall) {
536
- // The original value could have been either a JS object or a BSON buffer
537
- if (Buffer.isBuffer(original)) {
538
- original = deserialize(original);
539
- }
540
- if (Buffer.isBuffer(decrypted)) {
541
- throw new MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
542
- }
543
- }
544
-
545
- if (!decrypted || typeof decrypted !== 'object') return;
546
- for (const k of Object.keys(decrypted)) {
547
- const originalValue = original[k];
548
-
549
- // An object was decrypted by libmongocrypt if and only if it was
550
- // a BSON Binary object with subtype 6.
551
- if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
552
- if (!decrypted[kDecoratedKeys]) {
553
- Object.defineProperty(decrypted, kDecoratedKeys, {
554
- value: [],
555
- configurable: true,
556
- enumerable: false,
557
- writable: false
558
- });
559
- }
560
- // this is defined in the preceding if-statement
561
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
562
- decrypted[kDecoratedKeys]!.push(k);
563
- // Do not recurse into this decrypted value. It could be a sub-document/array,
564
- // in which case there is no original value associated with its subfields.
565
- continue;
566
- }
567
-
568
- decorateDecryptionResult(decrypted[k], originalValue, false);
569
- }
570
- }
@@ -5,7 +5,7 @@ import type {
5
5
  MongoCryptOptions
6
6
  } from 'mongodb-client-encryption';
7
7
 
8
- import { type Binary, type Document, type Long, serialize, type UUID } from '../bson';
8
+ import { type Binary, deserialize, type Document, type Long, serialize, type UUID } from '../bson';
9
9
  import { type AnyBulkWriteOperation, type BulkWriteResult } from '../bulk/common';
10
10
  import { type ProxyOptions } from '../cmap/connection';
11
11
  import { type Collection } from '../collection';
@@ -202,7 +202,7 @@ export class ClientEncryption {
202
202
  tlsOptions: this._tlsOptions
203
203
  });
204
204
 
205
- const dataKey = await stateMachine.execute<DataKey>(this, context);
205
+ const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
206
206
 
207
207
  const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
208
208
  this._keyVaultNamespace
@@ -259,7 +259,7 @@ export class ClientEncryption {
259
259
  tlsOptions: this._tlsOptions
260
260
  });
261
261
 
262
- const { v: dataKeys } = await stateMachine.execute<{ v: DataKey[] }>(this, context);
262
+ const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
263
263
  if (dataKeys.length === 0) {
264
264
  return {};
265
265
  }
@@ -640,7 +640,7 @@ export class ClientEncryption {
640
640
  tlsOptions: this._tlsOptions
641
641
  });
642
642
 
643
- const { v } = await stateMachine.execute<{ v: T }>(this, context);
643
+ const { v } = deserialize(await stateMachine.execute(this, context));
644
644
 
645
645
  return v;
646
646
  }
@@ -719,8 +719,8 @@ export class ClientEncryption {
719
719
  });
720
720
  const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
721
721
 
722
- const result = await stateMachine.execute<{ v: Binary }>(this, context);
723
- return result.v;
722
+ const { v } = deserialize(await stateMachine.execute(this, context));
723
+ return v;
724
724
  }
725
725
  }
726
726
 
@@ -12,7 +12,7 @@ import { loadGCPCredentials } from './gcp';
12
12
  * `aws:<name>`, `gcp:<name>`, `local:<name>`, `kmip:<name>`, `azure:<name>`
13
13
  * where `name` is an alphanumeric string, underscores allowed.
14
14
  */
15
- export type ClientEncryptionDataKeyProvider = string;
15
+ export type ClientEncryptionDataKeyProvider = keyof KMSProviders;
16
16
 
17
17
  /** @public */
18
18
  export interface AWSKMSProviderConfiguration {
@@ -122,34 +122,31 @@ export interface KMSProviders {
122
122
  * Configuration options for using 'aws' as your KMS provider
123
123
  */
124
124
  aws?: AWSKMSProviderConfiguration | Record<string, never>;
125
+ [key: `aws:${string}`]: AWSKMSProviderConfiguration;
125
126
 
126
127
  /**
127
128
  * Configuration options for using 'local' as your KMS provider
128
129
  */
129
130
  local?: LocalKMSProviderConfiguration;
131
+ [key: `local:${string}`]: LocalKMSProviderConfiguration;
130
132
 
131
133
  /**
132
134
  * Configuration options for using 'kmip' as your KMS provider
133
135
  */
134
136
  kmip?: KMIPKMSProviderConfiguration;
137
+ [key: `kmip:${string}`]: KMIPKMSProviderConfiguration;
135
138
 
136
139
  /**
137
140
  * Configuration options for using 'azure' as your KMS provider
138
141
  */
139
142
  azure?: AzureKMSProviderConfiguration | Record<string, never>;
143
+ [key: `azure:${string}`]: AzureKMSProviderConfiguration;
140
144
 
141
145
  /**
142
146
  * Configuration options for using 'gcp' as your KMS provider
143
147
  */
144
148
  gcp?: GCPKMSProviderConfiguration | Record<string, never>;
145
-
146
- [key: string]:
147
- | AWSKMSProviderConfiguration
148
- | LocalKMSProviderConfiguration
149
- | KMIPKMSProviderConfiguration
150
- | AzureKMSProviderConfiguration
151
- | GCPKMSProviderConfiguration
152
- | undefined;
149
+ [key: `gcp:${string}`]: GCPKMSProviderConfiguration;
153
150
  }
154
151
 
155
152
  /**
@@ -114,6 +114,19 @@ export type CSFLEKMSTlsOptions = {
114
114
  [key: string]: ClientEncryptionTlsOptions | undefined;
115
115
  };
116
116
 
117
+ /**
118
+ * This is kind of a hack. For `rewrapManyDataKey`, we have tests that
119
+ * guarantee that when there are no matching keys, `rewrapManyDataKey` returns
120
+ * nothing. We also have tests for auto encryption that guarantee for `encrypt`
121
+ * we return an error when there are no matching keys. This error is generated in
122
+ * subsequent iterations of the state machine.
123
+ * Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
124
+ * do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
125
+ * will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
126
+ * otherwise we'll return `{ v: [] }`.
127
+ */
128
+ let EMPTY_V;
129
+
117
130
  /**
118
131
  * @internal
119
132
  *
@@ -156,16 +169,13 @@ export class StateMachine {
156
169
  /**
157
170
  * Executes the state machine according to the specification
158
171
  */
159
- async execute<T extends Document>(
160
- executor: StateMachineExecutable,
161
- context: MongoCryptContext
162
- ): Promise<T> {
172
+ async execute(executor: StateMachineExecutable, context: MongoCryptContext): Promise<Uint8Array> {
163
173
  const keyVaultNamespace = executor._keyVaultNamespace;
164
174
  const keyVaultClient = executor._keyVaultClient;
165
175
  const metaDataClient = executor._metaDataClient;
166
176
  const mongocryptdClient = executor._mongocryptdClient;
167
177
  const mongocryptdManager = executor._mongocryptdManager;
168
- let result: T | null = null;
178
+ let result: Uint8Array | null = null;
169
179
 
170
180
  while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) {
171
181
  debug(`[context#${context.id}] ${stateToString.get(context.state) || context.state}`);
@@ -213,16 +223,8 @@ export class StateMachine {
213
223
  const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter);
214
224
 
215
225
  if (keys.length === 0) {
216
- // This is kind of a hack. For `rewrapManyDataKey`, we have tests that
217
- // guarantee that when there are no matching keys, `rewrapManyDataKey` returns
218
- // nothing. We also have tests for auto encryption that guarantee for `encrypt`
219
- // we return an error when there are no matching keys. This error is generated in
220
- // subsequent iterations of the state machine.
221
- // Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
222
- // do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
223
- // will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
224
- // otherwise we'll return `{ v: [] }`.
225
- result = { v: [] } as any as T;
226
+ // See docs on EMPTY_V
227
+ result = EMPTY_V ??= serialize({ v: [] });
226
228
  }
227
229
  for await (const key of keys) {
228
230
  context.addMongoOperationResponse(serialize(key));
@@ -254,7 +256,7 @@ export class StateMachine {
254
256
  const message = context.status.message || 'Finalization error';
255
257
  throw new MongoCryptError(message);
256
258
  }
257
- result = deserialize(finalizedContext, this.options) as T;
259
+ result = finalizedContext;
258
260
  break;
259
261
  }
260
262
 
@@ -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 { BSONSerializeOptions, Document, ObjectId } from '../bson';
5
- import type { AutoEncrypter } from '../client-side-encryption/auto_encrypter';
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
- isErrorResponse,
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.has('writeConcernError')) {
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
- if (error.name === 'MongoWriteConcernError') {
556
- this.emitAndLogCommand(
557
- this.monitorCommands,
558
- Connection.COMMAND_SUCCEEDED,
559
- message.databaseName,
560
- this.established,
561
- new CommandSucceededEvent(
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
- _responseType?: T | undefined
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, undefined);
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 response = await super.command<T>(ns, encrypted, options, undefined);
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 await autoEncrypter.decrypt(response, options);
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
- for (let i = 0; i < name.length; i++) {
82
- if (this.bson[nameOffset + i] !== name.charCodeAt(i)) return false;
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[index] && this.isElementName(name, element)) {
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?: false | undefined
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 */