@mongosh/service-provider-node-driver 2.3.3

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.
@@ -0,0 +1,1536 @@
1
+ import type {
2
+ Auth,
3
+ AuthMechanism,
4
+ ClientMetadata,
5
+ ReadPreferenceFromOptions,
6
+ ReadPreferenceLike,
7
+ OperationOptions,
8
+ RunCommandCursor,
9
+ RunCursorCommandOptions,
10
+ ClientEncryptionOptions,
11
+ MongoClient,
12
+ MongoMissingDependencyError,
13
+ SearchIndexDescription,
14
+ TopologyDescription,
15
+ TopologyDescriptionChangedEvent,
16
+ } from 'mongodb';
17
+
18
+ import type {
19
+ ServiceProvider,
20
+ ReplPlatform,
21
+ ShellAuthOptions,
22
+ // Driver types:
23
+ AggregateOptions,
24
+ AggregationCursor,
25
+ AnyBulkWriteOperation,
26
+ BulkWriteOptions,
27
+ BulkWriteResult,
28
+ ClientSessionOptions,
29
+ Collection,
30
+ CountDocumentsOptions,
31
+ CountOptions,
32
+ CreateCollectionOptions,
33
+ CreateIndexesOptions,
34
+ FindCursor,
35
+ Db,
36
+ DbOptions,
37
+ DeleteOptions,
38
+ DeleteResult,
39
+ DistinctOptions,
40
+ Document,
41
+ DropCollectionOptions,
42
+ DropDatabaseOptions,
43
+ EstimatedDocumentCountOptions,
44
+ FindOneAndDeleteOptions,
45
+ FindOneAndReplaceOptions,
46
+ FindOneAndUpdateOptions,
47
+ FindOptions,
48
+ IndexDescription,
49
+ InsertManyResult,
50
+ InsertOneOptions,
51
+ InsertOneResult,
52
+ ListCollectionsOptions,
53
+ ListDatabasesOptions,
54
+ ListIndexesOptions,
55
+ MongoClientOptions,
56
+ ReadConcern,
57
+ RenameOptions,
58
+ ReplaceOptions,
59
+ RunCommandOptions,
60
+ ClientSession,
61
+ UpdateOptions,
62
+ UpdateResult,
63
+ WriteConcern,
64
+ ChangeStreamOptions,
65
+ ChangeStream,
66
+ AutoEncryptionOptions,
67
+ ClientEncryption as MongoCryptClientEncryption,
68
+ ConnectionInfo,
69
+ } from '@mongosh/service-provider-core';
70
+ import {
71
+ getConnectExtraInfo,
72
+ DEFAULT_DB,
73
+ ServiceProviderCore,
74
+ } from '@mongosh/service-provider-core';
75
+
76
+ import type { DevtoolsConnectOptions } from '@mongodb-js/devtools-connect';
77
+ import { MongoshCommandFailed, MongoshInternalError } from '@mongosh/errors';
78
+ import type { MongoshBus } from '@mongosh/types';
79
+ import { forceCloseMongoClient } from './mongodb-patches';
80
+ import {
81
+ ConnectionString,
82
+ CommaAndColonSeparatedRecord,
83
+ } from 'mongodb-connection-string-url';
84
+ import { EventEmitter } from 'events';
85
+ import type { CreateEncryptedCollectionOptions } from '@mongosh/service-provider-core';
86
+ import type { DevtoolsConnectionState } from '@mongodb-js/devtools-connect';
87
+ import { isDeepStrictEqual } from 'util';
88
+ import * as driver from 'mongodb';
89
+ import {
90
+ MongoClient as MongoClientCtor,
91
+ ReadPreference,
92
+ ClientEncryption,
93
+ } from 'mongodb';
94
+ import { connectMongoClient } from '@mongodb-js/devtools-connect';
95
+
96
+ const bsonlib = () => {
97
+ const {
98
+ Binary,
99
+ Code,
100
+ DBRef,
101
+ Double,
102
+ Int32,
103
+ Long,
104
+ MinKey,
105
+ MaxKey,
106
+ ObjectId,
107
+ Timestamp,
108
+ Decimal128,
109
+ BSONSymbol,
110
+ BSONRegExp,
111
+ BSON,
112
+ } = driver;
113
+ return {
114
+ Binary,
115
+ Code,
116
+ DBRef,
117
+ Double,
118
+ Int32,
119
+ Long,
120
+ MinKey,
121
+ MaxKey,
122
+ ObjectId,
123
+ Timestamp,
124
+ Decimal128,
125
+ BSONSymbol,
126
+ calculateObjectSize: BSON.calculateObjectSize,
127
+ EJSON: BSON.EJSON,
128
+ BSONRegExp,
129
+ };
130
+ };
131
+
132
+ export type DropDatabaseResult = {
133
+ ok: 0 | 1;
134
+ dropped?: string;
135
+ };
136
+
137
+ /**
138
+ * Default driver options we always use.
139
+ */
140
+ const DEFAULT_DRIVER_OPTIONS: MongoClientOptions = Object.freeze({});
141
+
142
+ /**
143
+ * Default driver method options we always use.
144
+ */
145
+ const DEFAULT_BASE_OPTIONS: OperationOptions = Object.freeze({
146
+ serializeFunctions: true,
147
+ promoteLongs: false,
148
+ });
149
+
150
+ /**
151
+ * Pick properties of `uri` and `opts` that as a tuple that can be matched
152
+ * against the corresponding tuple for another `uri` and `opts` configuration,
153
+ * and when they do, it is meaningful to share connection state between them.
154
+ *
155
+ * Currently, this is only used for OIDC. We don't need to make sure that the
156
+ * configuration matches; what we *need* to avoid, however, is a case in which
157
+ * the same OIDC plugin instance gets passed to different connections whose
158
+ * endpoints don't have a trust relationship (i.e. different hosts) or where
159
+ * the usernames don't match, so that access tokens for one user on a given host
160
+ * do not end up being sent to a different host or for a different user.
161
+ */
162
+ function normalizeEndpointAndAuthConfiguration(
163
+ uri: ConnectionString,
164
+ opts: DevtoolsConnectOptions
165
+ ) {
166
+ const search = uri.typedSearchParams<DevtoolsConnectOptions>();
167
+ const authMechProps = new CommaAndColonSeparatedRecord(
168
+ search.get('authMechanismProperties')
169
+ );
170
+
171
+ return [
172
+ uri.protocol,
173
+ uri.hosts,
174
+ opts.auth?.username ?? uri.username,
175
+ opts.auth?.password ?? uri.password,
176
+ opts.authMechanism ?? search.get('authMechanism'),
177
+ opts.authSource ?? search.get('authSource'),
178
+ { ...Object.fromEntries(authMechProps), ...opts.authMechanismProperties },
179
+ ];
180
+ }
181
+
182
+ interface DependencyVersionInfo {
183
+ nodeDriverVersion?: string;
184
+ libmongocryptVersion?: string;
185
+ libmongocryptNodeBindingsVersion?: string;
186
+ kerberosVersion?: string;
187
+ }
188
+
189
+ /**
190
+ * Encapsulates logic for the service provider for the mongosh CLI.
191
+ */
192
+ export class NodeDriverServiceProvider
193
+ extends ServiceProviderCore
194
+ implements ServiceProvider
195
+ {
196
+ /**
197
+ * Create a new CLI service provider from the provided URI.
198
+ *
199
+ * @param {String} uri - The URI.
200
+ * @param {DevtoolsConnectOptions} driverOptions - The options.
201
+ * @param {Object} cliOptions - Options passed through CLI. Right now only being used for nodb.
202
+ *
203
+ * @returns {Promise} The promise with cli service provider.
204
+ */
205
+ static async connect(
206
+ this: typeof NodeDriverServiceProvider,
207
+ uri: string,
208
+ driverOptions: DevtoolsConnectOptions,
209
+ cliOptions: { nodb?: boolean } = {},
210
+ bus: MongoshBus = new EventEmitter() // TODO: Change VSCode to pass all arguments, then remove defaults
211
+ ): Promise<NodeDriverServiceProvider> {
212
+ const connectionString = new ConnectionString(uri || 'mongodb://nodb/');
213
+ const clientOptions = this.processDriverOptions(
214
+ null,
215
+ connectionString,
216
+ driverOptions
217
+ );
218
+ if (process.env.MONGOSH_TEST_FORCE_API_STRICT) {
219
+ clientOptions.serverApi = {
220
+ version:
221
+ typeof clientOptions.serverApi === 'string'
222
+ ? clientOptions.serverApi
223
+ : clientOptions.serverApi?.version ?? '1',
224
+ strict: true,
225
+ deprecationErrors: true,
226
+ };
227
+ }
228
+
229
+ let client: MongoClient;
230
+ let state: DevtoolsConnectionState | undefined;
231
+ let lastSeenTopology: TopologyDescription | undefined;
232
+
233
+ class MongoshMongoClient extends MongoClientCtor {
234
+ constructor(url: string, options?: MongoClientOptions) {
235
+ super(url, options);
236
+ this.on(
237
+ 'topologyDescriptionChanged',
238
+ (evt: TopologyDescriptionChangedEvent) => {
239
+ lastSeenTopology = evt.newDescription;
240
+ }
241
+ );
242
+ }
243
+ }
244
+
245
+ if (cliOptions.nodb) {
246
+ const clientOptionsCopy: MongoClientOptions &
247
+ Partial<DevtoolsConnectOptions> = {
248
+ ...clientOptions,
249
+ };
250
+ delete clientOptionsCopy.productName;
251
+ delete clientOptionsCopy.productDocsLink;
252
+ delete clientOptionsCopy.oidc;
253
+ delete clientOptionsCopy.parentHandle;
254
+ delete clientOptionsCopy.parentState;
255
+ delete clientOptionsCopy.proxy;
256
+ delete clientOptionsCopy.applyProxyToOIDC;
257
+ client = new MongoshMongoClient(
258
+ connectionString.toString(),
259
+ clientOptionsCopy
260
+ );
261
+ } else {
262
+ ({ client, state } = await connectMongoClient(
263
+ connectionString.toString(),
264
+ clientOptions,
265
+ bus,
266
+ MongoshMongoClient
267
+ ));
268
+ }
269
+ clientOptions.parentState = state;
270
+
271
+ return new this(
272
+ client,
273
+ bus,
274
+ clientOptions,
275
+ connectionString,
276
+ lastSeenTopology
277
+ );
278
+ }
279
+
280
+ public readonly platform: ReplPlatform;
281
+ public readonly initialDb: string;
282
+ public mongoClient: MongoClient; // public for testing
283
+ private readonly uri?: ConnectionString;
284
+ private currentClientOptions: DevtoolsConnectOptions;
285
+ private dbcache: WeakMap<MongoClient, Map<string, Db>>;
286
+ public baseCmdOptions: OperationOptions; // public for testing
287
+ private bus: MongoshBus;
288
+
289
+ /**
290
+ * Stores the last seen topology at the time when .connect() finishes.
291
+ */
292
+ private _lastSeenTopology: TopologyDescription | undefined;
293
+
294
+ /**
295
+ * Instantiate a new NodeDriverServiceProvider with the Node driver's connected
296
+ * MongoClient instance.
297
+ *
298
+ * @param {MongoClient} mongoClient - The Node drivers' MongoClient instance.
299
+ * @param {DevtoolsConnectOptions} clientOptions
300
+ * @param {string} uri - optional URI for telemetry.
301
+ */
302
+ constructor(
303
+ mongoClient: MongoClient,
304
+ bus: MongoshBus,
305
+ clientOptions: DevtoolsConnectOptions,
306
+ uri?: ConnectionString,
307
+ lastSeenTopology?: TopologyDescription
308
+ ) {
309
+ super(bsonlib());
310
+
311
+ this.bus = bus;
312
+ this.mongoClient = mongoClient;
313
+ this.uri = uri;
314
+ this._lastSeenTopology = lastSeenTopology;
315
+ this.platform = 'CLI';
316
+ try {
317
+ this.initialDb = (mongoClient as any).s.options.dbName || DEFAULT_DB;
318
+ } catch (err: any) {
319
+ this.initialDb = DEFAULT_DB;
320
+ }
321
+ this.currentClientOptions = clientOptions;
322
+ this.baseCmdOptions = { ...DEFAULT_BASE_OPTIONS }; // currently do not have any user-specified connection-wide command options, but I imagine we will eventually
323
+ this.dbcache = new WeakMap();
324
+ }
325
+
326
+ static getVersionInformation(): DependencyVersionInfo {
327
+ function tryCall<Fn extends () => any>(fn: Fn): ReturnType<Fn> | undefined {
328
+ try {
329
+ return fn();
330
+ } catch {
331
+ return;
332
+ }
333
+ }
334
+ return {
335
+ nodeDriverVersion: tryCall(() => require('mongodb/package.json').version),
336
+ libmongocryptVersion: tryCall(
337
+ () => ClientEncryption.libmongocryptVersion // getter that actually loads the native addon (!)
338
+ ),
339
+ libmongocryptNodeBindingsVersion: tryCall(
340
+ () => require('mongodb-client-encryption/package.json').version
341
+ ),
342
+ kerberosVersion: tryCall(() => require('kerberos/package.json').version),
343
+ };
344
+ }
345
+
346
+ maybeThrowBetterMissingOptionalDependencyError(
347
+ err: MongoMissingDependencyError
348
+ ): never {
349
+ if (err.message.includes('kerberos')) {
350
+ try {
351
+ require('kerberos');
352
+ } catch (cause) {
353
+ if (
354
+ typeof cause === 'object' &&
355
+ cause &&
356
+ 'message' in cause &&
357
+ typeof cause.message === 'string'
358
+ ) {
359
+ // @ts-expect-error `cause` is ES2022+
360
+ throw new Error(`Could not load kerberos package: ${cause.message}`, {
361
+ cause,
362
+ });
363
+ }
364
+ }
365
+ }
366
+ if (err.message.includes('mongodb-client-encryption')) {
367
+ try {
368
+ require('mongodb-client-encryption');
369
+ } catch (cause) {
370
+ if (
371
+ typeof cause === 'object' &&
372
+ cause &&
373
+ 'message' in cause &&
374
+ typeof cause.message === 'string'
375
+ ) {
376
+ // https://jira.mongodb.org/browse/MONGOSH-1216
377
+ const extra =
378
+ 'boxednode' in process
379
+ ? ''
380
+ : '\n(If you are installing mongosh through homebrew or npm, consider downlading mongosh from https://www.mongodb.com/try/download/shell instead)';
381
+ throw new Error(
382
+ `Could not load mongodb-client-encryption package: ${cause.message}${extra}`,
383
+ // @ts-expect-error `cause` is ES2022+
384
+ { cause }
385
+ );
386
+ }
387
+ }
388
+ }
389
+ throw err;
390
+ }
391
+
392
+ async connectMongoClient(
393
+ connectionString: ConnectionString | string,
394
+ clientOptions: DevtoolsConnectOptions
395
+ ): Promise<{ client: MongoClient; state: DevtoolsConnectionState }> {
396
+ try {
397
+ return await connectMongoClient(
398
+ connectionString.toString(),
399
+ clientOptions,
400
+ this.bus,
401
+ MongoClientCtor
402
+ );
403
+ } catch (err: unknown) {
404
+ if (
405
+ typeof err === 'object' &&
406
+ err &&
407
+ 'name' in err &&
408
+ err.name === 'MongoMissingDependencyError'
409
+ ) {
410
+ this.maybeThrowBetterMissingOptionalDependencyError(
411
+ err as MongoMissingDependencyError
412
+ );
413
+ }
414
+ throw err;
415
+ }
416
+ }
417
+
418
+ async getNewConnection(
419
+ uri: string,
420
+ options: Partial<DevtoolsConnectOptions> = {}
421
+ ): Promise<NodeDriverServiceProvider> {
422
+ const connectionString = new ConnectionString(uri);
423
+ const clientOptions = this.processDriverOptions(connectionString, options);
424
+
425
+ const { client, state } = await this.connectMongoClient(
426
+ connectionString.toString(),
427
+ clientOptions
428
+ );
429
+ clientOptions.parentState = state;
430
+ return new NodeDriverServiceProvider(
431
+ client,
432
+ this.bus,
433
+ clientOptions,
434
+ connectionString,
435
+ this._lastSeenTopology
436
+ );
437
+ }
438
+
439
+ _getHostnameForConnection(
440
+ topology?: TopologyDescription
441
+ ): string | undefined {
442
+ return topology?.servers?.values().next().value.hostAddress.host;
443
+ }
444
+
445
+ async getConnectionInfo(): Promise<ConnectionInfo> {
446
+ const [buildInfo = null, atlasVersion = null, fcv = null, atlascliInfo] =
447
+ await Promise.all([
448
+ this.runCommandWithCheck(
449
+ 'admin',
450
+ { buildInfo: 1 },
451
+ this.baseCmdOptions
452
+ ).catch(() => {}),
453
+ this.runCommandWithCheck(
454
+ 'admin',
455
+ { atlasVersion: 1 },
456
+ this.baseCmdOptions
457
+ ).catch(() => {}),
458
+ this.runCommandWithCheck(
459
+ 'admin',
460
+ { getParameter: 1, featureCompatibilityVersion: 1 },
461
+ this.baseCmdOptions
462
+ ).catch(() => {}),
463
+ this.countDocuments('admin', 'atlascli', {
464
+ managedClusterType: 'atlasCliLocalDevCluster',
465
+ }).catch(() => 0),
466
+ ]);
467
+
468
+ const resolvedHostname = this._getHostnameForConnection(
469
+ this._lastSeenTopology
470
+ );
471
+
472
+ const extraConnectionInfo = getConnectExtraInfo({
473
+ connectionString: this.uri,
474
+ buildInfo,
475
+ atlasVersion,
476
+ resolvedHostname,
477
+ isLocalAtlas: !!atlascliInfo,
478
+ });
479
+
480
+ return {
481
+ buildInfo,
482
+ resolvedHostname,
483
+ extraInfo: {
484
+ ...extraConnectionInfo,
485
+ fcv: fcv?.featureCompatibilityVersion?.version,
486
+ },
487
+ };
488
+ }
489
+
490
+ async renameCollection(
491
+ database: string,
492
+ oldName: string,
493
+ newName: string,
494
+ options: RenameOptions = {},
495
+ dbOptions?: DbOptions
496
+ ): Promise<Collection> {
497
+ options = { ...this.baseCmdOptions, ...options };
498
+ return await this.db(database, dbOptions).renameCollection(
499
+ oldName,
500
+ newName,
501
+ options
502
+ );
503
+ }
504
+
505
+ /**
506
+ * Get the Db object from the client.
507
+ *
508
+ * @param {String} name - The database name.
509
+ * @param dbOptions
510
+ *
511
+ * @returns {Db} The database.
512
+ */
513
+ private db(name: string, dbOptions: DbOptions = {}): Db {
514
+ const key = `${name}-${JSON.stringify(dbOptions)}`;
515
+ const dbcache = this.getDBCache();
516
+ const cached = dbcache.get(key);
517
+ if (cached) {
518
+ return cached;
519
+ }
520
+ const db = this.mongoClient.db(name, dbOptions);
521
+ dbcache.set(key, db);
522
+ return db;
523
+ }
524
+
525
+ /**
526
+ * Wrapper to make this available for testing.
527
+ */
528
+ _dbTestWrapper(name: string, dbOptions?: DbOptions): Db {
529
+ return this.db(name, dbOptions);
530
+ }
531
+
532
+ /**
533
+ * Return the db cache for the current MongoClient.
534
+ */
535
+ private getDBCache(): Map<string, Db> {
536
+ const existing = this.dbcache.get(this.mongoClient);
537
+ if (existing) {
538
+ return existing;
539
+ }
540
+ this.dbcache.set(this.mongoClient, new Map());
541
+ return this.getDBCache();
542
+ }
543
+
544
+ /**
545
+ * Run an aggregation pipeline.
546
+ *
547
+ * @param {String} database - the db name
548
+ * @param {String} collection - the collection name
549
+ * @param pipeline
550
+ * @param options
551
+ * allowDiskUse: Optional<Boolean>;
552
+ * batchSize: Optional<Int32>;
553
+ * bypassDocumentValidation: Optional<Boolean>;
554
+ * collation: Optional<Document>;
555
+ * maxTimeMS: Optional<Int64>;
556
+ * maxAwaitTimeMS: Optional<Int64>;
557
+ * comment: Optional<String>;
558
+ * hint: Optional<(String | Document = {})>;
559
+ * @param dbOptions
560
+
561
+ * @returns {Cursor} The aggregation cursor.
562
+ */
563
+ aggregate(
564
+ database: string,
565
+ collection: string,
566
+ pipeline: Document[] = [],
567
+ options: AggregateOptions = {},
568
+ dbOptions?: DbOptions
569
+ ): AggregationCursor {
570
+ options = { ...this.baseCmdOptions, ...options };
571
+ return this.db(database, dbOptions)
572
+ .collection(collection)
573
+ .aggregate(pipeline, options);
574
+ }
575
+
576
+ /**
577
+ * @param {String} database - the db name
578
+ * @param pipeline
579
+ * @param options
580
+ * allowDiskUse: Optional<Boolean>;
581
+ * batchSize: Optional<Int32>;
582
+ * bypassDocumentValidation: Optional<Boolean>;
583
+ * collation: Optional<Document>;
584
+ * maxTimeMS: Optional<Int64>;
585
+ * maxAwaitTimeMS: Optional<Int64>;
586
+ * comment: Optional<String>;
587
+ * hint: Optional<(String | Document = {})>;
588
+ * @param dbOptions
589
+ * j: Optional<Boolean>
590
+ * w: Optional<Int32 | String>
591
+ * wtimeoutMS: Optional<Int64>
592
+ * @return {any}
593
+ */
594
+ aggregateDb(
595
+ database: string,
596
+ pipeline: Document[] = [],
597
+ options: AggregateOptions = {},
598
+ dbOptions?: DbOptions
599
+ ): AggregationCursor {
600
+ options = { ...this.baseCmdOptions, ...options };
601
+ const db: any = this.db(database, dbOptions) as any;
602
+ return db.aggregate(pipeline, options);
603
+ }
604
+
605
+ /**
606
+ * @param {String} database - the db name
607
+ * @param {String} collection - the collection name
608
+ * @param requests
609
+ * @param options
610
+ * ordered: Boolean;
611
+ * bypassDocumentValidation: Optional<Boolean>;
612
+ * @param dbOptions
613
+ * j: Optional<Boolean>
614
+ * w: Optional<Int32 | String>
615
+ * wtimeoutMS: Optional<Int64>
616
+ * readConcern:
617
+ * level: <String local|majority|linearizable|available>
618
+ * @return {any}
619
+ */
620
+ bulkWrite(
621
+ database: string,
622
+ collection: string,
623
+ requests: AnyBulkWriteOperation[],
624
+ options: BulkWriteOptions = {},
625
+ dbOptions?: DbOptions
626
+ ): Promise<BulkWriteResult> {
627
+ options = { ...this.baseCmdOptions, ...options };
628
+ return this.db(database, dbOptions)
629
+ .collection(collection)
630
+ .bulkWrite(requests, options);
631
+ }
632
+
633
+ /**
634
+ * Close the connection.
635
+ *
636
+ * @param {boolean} force - Whether to force close the connection.
637
+ */
638
+ async close(force: boolean): Promise<void> {
639
+ this.dbcache.set(this.mongoClient, new Map());
640
+ if (force) {
641
+ await forceCloseMongoClient(this.mongoClient);
642
+ } else {
643
+ await this.mongoClient.close();
644
+ }
645
+ }
646
+
647
+ async suspend(): Promise<() => Promise<void>> {
648
+ await this.close(true);
649
+ return async () => {
650
+ await this.resetConnectionOptions({});
651
+ };
652
+ }
653
+
654
+ /**
655
+ * Deprecated count command.
656
+ *
657
+ * @param {String} database - the db name
658
+ * @param {String} collection - the collection name
659
+ * @param query
660
+ * @param options
661
+ * collation: Optional<Document>
662
+ * hint: Optional<(String | Document = {})>;
663
+ * limit: Optional<Int64>;
664
+ * maxTimeMS: Optional<Int64>;
665
+ * skip: Optional<Int64>;
666
+ * @param dbOptions
667
+ * readConcern:
668
+ * level: <String local|majority|linearizable|available>
669
+ * @return {Promise<any>}
670
+ */
671
+ count(
672
+ database: string,
673
+ collection: string,
674
+ query: Document = {},
675
+ options: CountOptions = {},
676
+ dbOptions?: DbOptions
677
+ ): Promise<number> {
678
+ options = { ...this.baseCmdOptions, ...options };
679
+ return this.db(database, dbOptions)
680
+ .collection(collection)
681
+ .count(query, options);
682
+ }
683
+
684
+ /**
685
+ * Get an exact document count from the collection.
686
+ *
687
+ * @param {String} database - the db name
688
+ * @param {String} collection - the collection name
689
+ * @param filter
690
+ * @param options
691
+ * hint: Optional<(String | Document = {})>;
692
+ * limit: Optional<Int64>;
693
+ * maxTimeMS: Optional<Int64>;
694
+ * skip: Optional<Int64>;
695
+ * @param dbOptions
696
+ * @return {any}
697
+ */
698
+ countDocuments(
699
+ database: string,
700
+ collection: string,
701
+ filter: Document = {},
702
+ options: CountDocumentsOptions = {},
703
+ dbOptions?: DbOptions
704
+ ): Promise<number> {
705
+ options = { ...this.baseCmdOptions, ...options };
706
+ return this.db(database, dbOptions)
707
+ .collection(collection)
708
+ .countDocuments(filter, options);
709
+ }
710
+
711
+ /**
712
+ * Delete multiple documents from the collection.
713
+ *
714
+ * @param {String} database - The database name.
715
+ * @param {String} collection - The collection name.
716
+ * @param {Object} filter - The filter.
717
+ * @param {Object} options - The delete many options.
718
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
719
+ *
720
+ * @returns {Promise} The promise of the result.
721
+ */
722
+ deleteMany(
723
+ database: string,
724
+ collection: string,
725
+ filter: Document = {},
726
+ options: DeleteOptions = {},
727
+ dbOptions?: DbOptions
728
+ ): Promise<DeleteResult> {
729
+ options = { ...this.baseCmdOptions, ...options };
730
+ return this.db(database, dbOptions)
731
+ .collection(collection)
732
+ .deleteMany(filter, options);
733
+ }
734
+
735
+ /**
736
+ * Delete one document from the collection.
737
+ *
738
+ * @param {String} database - The database name.
739
+ * @param {String} collection - The collection name.
740
+ * @param {Object} filter - The filter.
741
+ * @param {Object} options - The delete one options.
742
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
743
+ *
744
+ * @returns {Promise} The promise of the result.
745
+ */
746
+ deleteOne(
747
+ database: string,
748
+ collection: string,
749
+ filter: Document = {},
750
+ options: DeleteOptions = {},
751
+ dbOptions?: DbOptions
752
+ ): Promise<DeleteResult> {
753
+ options = { ...this.baseCmdOptions, ...options };
754
+ return this.db(database, dbOptions)
755
+ .collection(collection)
756
+ .deleteOne(filter, options);
757
+ }
758
+
759
+ /**
760
+ * Get distinct values for the field.
761
+ *
762
+ * @param {String} database - The database name.
763
+ * @param {String} collection - The collection name.
764
+ * @param {String} fieldName - The field name.
765
+ * @param {Object} filter - The filter.
766
+ * @param {Object} options - The distinct options.
767
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
768
+ *
769
+ * @returns {Cursor} The cursor.
770
+ */
771
+ distinct(
772
+ database: string,
773
+ collection: string,
774
+ fieldName: string,
775
+ filter: Document = {},
776
+ options: DistinctOptions = {},
777
+ dbOptions?: DbOptions
778
+ ): Promise<Document[]> {
779
+ options = { ...this.baseCmdOptions, ...options };
780
+ return this.db(database, dbOptions)
781
+ .collection(collection)
782
+ .distinct(fieldName, filter, options);
783
+ }
784
+
785
+ /**
786
+ * Get an estimated document count from the collection.
787
+ *
788
+ * @param {String} database - The database name.
789
+ * @param {String} collection - The collection name.
790
+ * @param {Object} options - The count options.
791
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
792
+ *
793
+ * @returns {Promise} The promise of the result.
794
+ */
795
+ estimatedDocumentCount(
796
+ database: string,
797
+ collection: string,
798
+ options: EstimatedDocumentCountOptions = {},
799
+ dbOptions?: DbOptions
800
+ ): Promise<number> {
801
+ options = { ...this.baseCmdOptions, ...options };
802
+ return this.db(database, dbOptions)
803
+ .collection(collection)
804
+ .estimatedDocumentCount(options);
805
+ }
806
+
807
+ /**
808
+ * Find documents in the collection.
809
+ *
810
+ * @param {String} database - The database name.
811
+ * @param {String} collection - The collection name.
812
+ * @param {Object} filter - The filter.
813
+ * @param {Object} options - The find options.
814
+ *
815
+ * @param dbOptions
816
+ * @returns {Cursor} The cursor.
817
+ */
818
+ find(
819
+ database: string,
820
+ collection: string,
821
+ filter: Document = {},
822
+ options: FindOptions = {},
823
+ dbOptions?: DbOptions
824
+ ): FindCursor {
825
+ const findOptions: any = { ...this.baseCmdOptions, ...options };
826
+ if ('allowPartialResults' in findOptions) {
827
+ findOptions.partial = findOptions.allowPartialResults;
828
+ }
829
+ if ('noCursorTimeout' in findOptions) {
830
+ findOptions.timeout = findOptions.noCursorTimeout;
831
+ }
832
+ return this.db(database, dbOptions)
833
+ .collection(collection)
834
+ .find(filter, findOptions);
835
+ }
836
+
837
+ /**
838
+ * Find one document and delete it.
839
+ *
840
+ * @param {String} database - The database name.
841
+ * @param {String} collection - The collection name.
842
+ * @param {Object} filter - The filter.
843
+ * @param {Object} options - The find options.
844
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
845
+ *
846
+ * @returns {Promise} The promise of the result.
847
+ */
848
+ findOneAndDelete(
849
+ database: string,
850
+ collection: string,
851
+ filter: Document = {},
852
+ options: FindOneAndDeleteOptions = {},
853
+ dbOptions?: DbOptions
854
+ ): Promise<Document | null> {
855
+ // TODO(MONGOSH-XXX): Consider removing the includeResultMetadata default
856
+ // since `false` is what gives the spec-compliant driver behavior.
857
+ options = {
858
+ includeResultMetadata: true,
859
+ ...this.baseCmdOptions,
860
+ ...options,
861
+ };
862
+ return this.db(database, dbOptions)
863
+ .collection(collection)
864
+ .findOneAndDelete(filter, options);
865
+ }
866
+
867
+ /**
868
+ * Find one document and replace it.
869
+ *
870
+ * @param {String} database - The database name.
871
+ * @param {String} collection - The collection name.
872
+ * @param {Object} filter - The filter.
873
+ * @param {Object} replacement - The replacement.
874
+ * @param {Object} options - The find options.
875
+ *
876
+ * @param dbOptions
877
+ * @returns {Promise} The promise of the result.
878
+ */
879
+ findOneAndReplace(
880
+ database: string,
881
+ collection: string,
882
+ filter: Document = {},
883
+ replacement: Document = {},
884
+ options: FindOneAndReplaceOptions = {},
885
+ dbOptions?: DbOptions
886
+ ): Promise<Document> {
887
+ const findOneAndReplaceOptions: any = {
888
+ includeResultMetadata: true,
889
+ ...this.baseCmdOptions,
890
+ ...options,
891
+ };
892
+
893
+ return (
894
+ this.db(database, dbOptions).collection(collection) as any
895
+ ).findOneAndReplace(filter, replacement, findOneAndReplaceOptions);
896
+ }
897
+
898
+ /**
899
+ * Find one document and update it.
900
+ *
901
+ * @param {String} database - The database name.
902
+ * @param {String} collection - The collection name.
903
+ * @param {Object} filter - The filter.
904
+ * @param {(Object|Array)} update - The update.
905
+ * @param {Object} options - The find options.
906
+ *
907
+ * @param dbOptions
908
+ * @returns {Promise} The promise of the result.
909
+ */
910
+ findOneAndUpdate(
911
+ database: string,
912
+ collection: string,
913
+ filter: Document = {},
914
+ update: Document | Document[] = {},
915
+ options: FindOneAndUpdateOptions = {},
916
+ dbOptions?: DbOptions
917
+ ): Promise<Document> {
918
+ const findOneAndUpdateOptions = {
919
+ includeResultMetadata: true,
920
+ ...this.baseCmdOptions,
921
+ ...options,
922
+ };
923
+
924
+ return this.db(database, dbOptions)
925
+ .collection(collection)
926
+ .findOneAndUpdate(filter, update, findOneAndUpdateOptions) as any;
927
+ }
928
+
929
+ /**
930
+ * Insert many documents into the collection.
931
+ *
932
+ * @param {string} database - The database name.
933
+ * @param {string} collection - The collection name.
934
+ * @param {Document[]} [docs=[]] - The documents.
935
+ * @param {Document} [options={}] - options - The insert many options.
936
+ * @param {DbOptions} [dbOptions] - The database options.
937
+ *
938
+ * @returns {Promise<InsertManyResult>}
939
+ */
940
+ insertMany(
941
+ database: string,
942
+ collection: string,
943
+ docs: Document[] = [],
944
+ options: BulkWriteOptions = {},
945
+ dbOptions?: DbOptions
946
+ ): Promise<InsertManyResult> {
947
+ options = { ...this.baseCmdOptions, ...options };
948
+ return this.db(database, dbOptions)
949
+ .collection(collection)
950
+ .insertMany(docs, options);
951
+ }
952
+
953
+ /**
954
+ * Insert one document into the collection.
955
+ *
956
+ * @param {String} database - The database name.
957
+ * @param {String} collection - The collection name.
958
+ * @param {Object} doc - The document.
959
+ * @param {Object} options - The insert one options.
960
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
961
+ *
962
+ * @returns {Promise} The promise of the result.
963
+ */
964
+ async insertOne(
965
+ database: string,
966
+ collection: string,
967
+ doc: Document = {},
968
+ options: InsertOneOptions = {},
969
+ dbOptions?: DbOptions
970
+ ): Promise<InsertOneResult> {
971
+ options = { ...this.baseCmdOptions, ...options };
972
+ return this.db(database, dbOptions)
973
+ .collection(collection)
974
+ .insertOne(doc, options);
975
+ }
976
+
977
+ /**
978
+ * Replace a document with another.
979
+ *
980
+ * @param {String} database - The database name.
981
+ * @param {String} collection - The collection name.
982
+ * @param {Object} filter - The filter.
983
+ * @param {Object} replacement - The replacement document for matches.
984
+ * @param {Object} options - The replace options.
985
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
986
+ *
987
+ * @returns {Promise} The promise of the result.
988
+ */
989
+ replaceOne(
990
+ database: string,
991
+ collection: string,
992
+ filter: Document = {},
993
+ replacement: Document = {},
994
+ options: ReplaceOptions = {},
995
+ dbOptions?: DbOptions
996
+ ): Promise<UpdateResult> {
997
+ options = { ...this.baseCmdOptions, ...options };
998
+ return this.db(database, dbOptions)
999
+ .collection(collection)
1000
+ .replaceOne(filter, replacement, options) as Promise<UpdateResult>;
1001
+ // `as UpdateResult` because we know we didn't request .explain() here.
1002
+ }
1003
+
1004
+ /**
1005
+ * Run a command against the database.
1006
+ *
1007
+ * @param {String} database - The database name.
1008
+ * @param {Object} spec - The command specification.
1009
+ * @param {Object} options - The database options.
1010
+ * @param {Object} dbOptions - The connection-wide database options.
1011
+ *
1012
+ * @returns {Promise} The promise of command results.
1013
+ */
1014
+ runCommand(
1015
+ database: string,
1016
+ spec: Document = {},
1017
+ options: RunCommandOptions = {},
1018
+ dbOptions?: DbOptions
1019
+ ): Promise<Document> {
1020
+ options = { ...this.baseCmdOptions, ...options };
1021
+ const db = this.db(database, dbOptions);
1022
+ return db.command(spec, options);
1023
+ }
1024
+
1025
+ /**
1026
+ * Run a command against the database and check the results for ok: 0.
1027
+ *
1028
+ * @param {String} database - The database name.
1029
+ * @param {Object} spec - The command specification.
1030
+ * @param {Object} options - The database options.
1031
+ * @param {Object} dbOptions - The connection-wide database options.
1032
+ *
1033
+ * @returns {Promise} The promise of command results.
1034
+ */
1035
+ async runCommandWithCheck(
1036
+ database: string,
1037
+ spec: Document = {},
1038
+ options: RunCommandOptions = {},
1039
+ dbOptions?: DbOptions
1040
+ ): Promise<Document> {
1041
+ const result = await this.runCommand(database, spec, options, dbOptions);
1042
+ if (result.ok === 0) {
1043
+ throw new MongoshCommandFailed(JSON.stringify(spec));
1044
+ }
1045
+ return result as { ok: 1 };
1046
+ }
1047
+
1048
+ /**
1049
+ * Run a command against the database that returns a cursor.
1050
+ *
1051
+ * @param {String} database - The database name.
1052
+ * @param {Object} spec - The command specification.
1053
+ * @param {Object} options - The command options.
1054
+ * @param {Object} dbOptions - The connection-wide database options.
1055
+ */
1056
+ runCursorCommand(
1057
+ database: string,
1058
+ spec: Document = {},
1059
+ options: RunCursorCommandOptions = {},
1060
+ dbOptions?: DbOptions
1061
+ ): RunCommandCursor {
1062
+ options = { ...this.baseCmdOptions, ...options };
1063
+ const db = this.db(database, dbOptions);
1064
+ return db.runCursorCommand(spec, options);
1065
+ }
1066
+
1067
+ /**
1068
+ * list databases.
1069
+ *
1070
+ * @param {String} database - The database name.
1071
+ *
1072
+ * @returns {Promise} The promise of command results.
1073
+ */
1074
+ listDatabases(
1075
+ database: string,
1076
+ options: ListDatabasesOptions = {}
1077
+ ): Promise<Document> {
1078
+ options = { ...this.baseCmdOptions, ...options };
1079
+ return this.db(database).admin().listDatabases(options);
1080
+ }
1081
+
1082
+ /**
1083
+ * Update many document.
1084
+ *
1085
+ * @param {String} database - The database name.
1086
+ * @param {String} collection - The collection name.
1087
+ * @param {Object} filter - The filter.
1088
+ * @param {(Object|Array)} update - The updates.
1089
+ * @param {Object} options - The update options.
1090
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
1091
+ *
1092
+ * @returns {Promise} The promise of the result.
1093
+ */
1094
+ async updateMany(
1095
+ database: string,
1096
+ collection: string,
1097
+ filter: Document = {},
1098
+ update: Document = {},
1099
+ options: UpdateOptions = {},
1100
+ dbOptions?: DbOptions
1101
+ ): Promise<UpdateResult> {
1102
+ options = { ...this.baseCmdOptions, ...options };
1103
+ return await this.db(database, dbOptions)
1104
+ .collection(collection)
1105
+ .updateMany(filter, update, options);
1106
+ }
1107
+
1108
+ /**
1109
+ * Update a document.
1110
+ *
1111
+ * @param {String} database - The database name.
1112
+ * @param {String} collection - The collection name.
1113
+ * @param {Object} filter - The filter.
1114
+ * @param {(Object|Array)} update - The updates.
1115
+ * @param {Object} options - The update options.
1116
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
1117
+ *
1118
+ * @returns {Promise} The promise of the result.
1119
+ */
1120
+ updateOne(
1121
+ database: string,
1122
+ collection: string,
1123
+ filter: Document = {},
1124
+ update: Document = {},
1125
+ options: UpdateOptions = {},
1126
+ dbOptions?: DbOptions
1127
+ ): Promise<UpdateResult> {
1128
+ options = { ...this.baseCmdOptions, ...options };
1129
+ return this.db(database, dbOptions)
1130
+ .collection(collection)
1131
+ .updateOne(filter, update, options);
1132
+ }
1133
+
1134
+ /**
1135
+ * Get currently known topology information.
1136
+ */
1137
+ getTopology(): any | undefined {
1138
+ return (this.mongoClient as any).topology;
1139
+ }
1140
+
1141
+ /**
1142
+ * Drop a database
1143
+ *
1144
+ * @param {String} db - The database name.
1145
+ * @param {Document} options - The write concern.
1146
+ *
1147
+ * @param dbOptions
1148
+ * @returns {Promise<Document>} The result of the operation.
1149
+ */
1150
+ async dropDatabase(
1151
+ db: string,
1152
+ options: DropDatabaseOptions = {},
1153
+ dbOptions: DbOptions = {}
1154
+ ): Promise<DropDatabaseResult> {
1155
+ const opts = { ...this.baseCmdOptions, ...options } as DropDatabaseOptions;
1156
+ const nativeResult = await this.db(db, dbOptions).dropDatabase(opts);
1157
+
1158
+ const ok = nativeResult ? 1 : 0;
1159
+ return {
1160
+ ok,
1161
+ ...(ok ? { dropped: db } : {}),
1162
+ };
1163
+ }
1164
+
1165
+ /**
1166
+ * Adds new indexes to a collection.
1167
+ *
1168
+ * @param {String} database - The db name.
1169
+ * @param {String} collection - The collection name.
1170
+ * @param {Object[]} indexSpecs the spec of the intexes to be created.
1171
+ * @param {Object} options - The command options.
1172
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
1173
+ * @return {Promise}
1174
+ */
1175
+ async createIndexes(
1176
+ database: string,
1177
+ collection: string,
1178
+ indexSpecs: IndexDescription[],
1179
+ options: CreateIndexesOptions = {},
1180
+ dbOptions?: DbOptions
1181
+ ): Promise<string[]> {
1182
+ options = { ...this.baseCmdOptions, ...options };
1183
+ return this.db(database, dbOptions)
1184
+ .collection(collection)
1185
+ .createIndexes(indexSpecs, options);
1186
+ }
1187
+
1188
+ /**
1189
+ * Returns an array that holds a list of documents that identify and
1190
+ * describe the existing indexes on the collection.
1191
+ *
1192
+ * @param {String} database - The db name.
1193
+ * @param {String} collection - The collection name.
1194
+ * @param options
1195
+ * @param {Object} dbOptions - The database options
1196
+ * (i.e. readConcern, writeConcern. etc).
1197
+ *
1198
+ * @return {Promise}
1199
+ */
1200
+ async getIndexes(
1201
+ database: string,
1202
+ collection: string,
1203
+ options: ListIndexesOptions = {},
1204
+ dbOptions?: DbOptions
1205
+ ): Promise<Document[]> {
1206
+ return await this.db(database, dbOptions)
1207
+ .collection(collection)
1208
+ .listIndexes({ ...this.baseCmdOptions, ...options })
1209
+ .toArray();
1210
+ }
1211
+
1212
+ /**
1213
+ * Returns an array of collection infos
1214
+ *
1215
+ * @param {String} database - The db name.
1216
+ * @param {Document} filter - The filter.
1217
+ * @param {Document} options - The command options.
1218
+ * @param {Object} dbOptions - The database options
1219
+ * (i.e. readConcern, writeConcern. etc).
1220
+ *
1221
+ * @return {Promise}
1222
+ */
1223
+ async listCollections(
1224
+ database: string,
1225
+ filter: Document = {},
1226
+ options: ListCollectionsOptions = {},
1227
+ dbOptions?: DbOptions
1228
+ ): Promise<Document[]> {
1229
+ options = { ...this.baseCmdOptions, ...options };
1230
+ return await this.db(database, dbOptions)
1231
+ .listCollections(filter, options)
1232
+ .toArray();
1233
+ }
1234
+
1235
+ /**
1236
+ * Drops a the collection.
1237
+ *
1238
+ * @param {String} database - The db name.
1239
+ * @param {String} collection - The collection name.
1240
+ * @param options
1241
+ * @param {Object} dbOptions - The database options (i.e. readConcern, writeConcern. etc).
1242
+ *
1243
+ * @return {Promise}
1244
+ */
1245
+ async dropCollection(
1246
+ database: string,
1247
+ collection: string,
1248
+ options: DropCollectionOptions = {},
1249
+ dbOptions?: DbOptions
1250
+ ): Promise<boolean> {
1251
+ return this.db(database, dbOptions)
1252
+ .collection(collection)
1253
+ .drop({ ...this.baseCmdOptions, ...options });
1254
+ }
1255
+
1256
+ /**
1257
+ * Authenticate
1258
+ *
1259
+ * @param authDoc
1260
+ */
1261
+ async authenticate(authDoc: ShellAuthOptions): Promise<{ ok: 1 }> {
1262
+ // NOTE: we keep all the original options and just overwrite the auth ones.
1263
+ const auth: Auth = { username: authDoc.user, password: authDoc.pwd };
1264
+ await this.resetConnectionOptions({
1265
+ auth,
1266
+ ...(authDoc.mechanism
1267
+ ? { authMechanism: authDoc.mechanism as AuthMechanism }
1268
+ : {}),
1269
+ ...(authDoc.authDb ? { authSource: authDoc.authDb } : {}),
1270
+ });
1271
+ return { ok: 1 };
1272
+ }
1273
+
1274
+ async createCollection(
1275
+ dbName: string,
1276
+ collName: string,
1277
+ options: CreateCollectionOptions = {},
1278
+ dbOptions?: DbOptions
1279
+ ): Promise<{ ok: number }> {
1280
+ options = { ...this.baseCmdOptions, ...options };
1281
+ await this.db(dbName, dbOptions).createCollection(collName, options);
1282
+ return { ok: 1 };
1283
+ }
1284
+
1285
+ async createEncryptedCollection(
1286
+ dbName: string,
1287
+ collName: string,
1288
+ options: CreateEncryptedCollectionOptions,
1289
+ libmongocrypt: MongoCryptClientEncryption
1290
+ ): Promise<{ collection: Collection; encryptedFields: Document }> {
1291
+ return await libmongocrypt.createEncryptedCollection(
1292
+ this.db(dbName),
1293
+ collName,
1294
+ options
1295
+ );
1296
+ }
1297
+
1298
+ // eslint-disable-next-line @typescript-eslint/require-await
1299
+ async initializeBulkOp(
1300
+ dbName: string,
1301
+ collName: string,
1302
+ ordered: boolean,
1303
+ options: BulkWriteOptions = {},
1304
+ dbOptions?: DbOptions
1305
+ ): Promise<any> {
1306
+ // Update to actual type after https://jira.mongodb.org/browse/MONGOSH-915
1307
+ if (ordered) {
1308
+ return this.db(dbName, dbOptions)
1309
+ .collection(collName)
1310
+ .initializeOrderedBulkOp(options);
1311
+ }
1312
+ return this.db(dbName, dbOptions)
1313
+ .collection(collName)
1314
+ .initializeUnorderedBulkOp(options);
1315
+ }
1316
+
1317
+ getReadPreference(): ReadPreference {
1318
+ return this.mongoClient.readPreference;
1319
+ }
1320
+
1321
+ getReadConcern(): ReadConcern | undefined {
1322
+ return this.mongoClient.readConcern;
1323
+ }
1324
+
1325
+ getWriteConcern(): WriteConcern | undefined {
1326
+ return this.mongoClient.writeConcern;
1327
+ }
1328
+
1329
+ readPreferenceFromOptions(
1330
+ options?: Omit<ReadPreferenceFromOptions, 'session'>
1331
+ ): ReadPreferenceLike | undefined {
1332
+ return ReadPreference.fromOptions(options);
1333
+ }
1334
+
1335
+ /**
1336
+ * For instances where a user wants to set a option that requires a new MongoClient.
1337
+ *
1338
+ * @param options
1339
+ */
1340
+ async resetConnectionOptions(options: MongoClientOptions): Promise<void> {
1341
+ this.bus.emit('mongosh-sp:reset-connection-options');
1342
+ this.currentClientOptions = {
1343
+ ...this.currentClientOptions,
1344
+ ...options,
1345
+ };
1346
+ const clientOptions = this.processDriverOptions(
1347
+ this.uri as ConnectionString,
1348
+ this.currentClientOptions
1349
+ );
1350
+ const { client, state } = await this.connectMongoClient(
1351
+ (this.uri as ConnectionString).toString(),
1352
+ clientOptions
1353
+ );
1354
+ try {
1355
+ await this.mongoClient.close();
1356
+ // eslint-disable-next-line no-empty
1357
+ } catch {}
1358
+ this.mongoClient = client;
1359
+ this.currentClientOptions.parentState = state;
1360
+ }
1361
+
1362
+ startSession(options: ClientSessionOptions): ClientSession {
1363
+ return this.mongoClient.startSession(options);
1364
+ }
1365
+
1366
+ watch(
1367
+ pipeline: Document[],
1368
+ options: ChangeStreamOptions,
1369
+ dbOptions: DbOptions = {},
1370
+ db?: string,
1371
+ coll?: string
1372
+ ): ChangeStream<Document> {
1373
+ if (db === undefined && coll === undefined) {
1374
+ // TODO: watch not exported, see NODE-2934
1375
+ return (this.mongoClient as any).watch(pipeline, options);
1376
+ } else if (db !== undefined && coll === undefined) {
1377
+ return (this.db(db, dbOptions) as any).watch(pipeline, options);
1378
+ } else if (db !== undefined && coll !== undefined) {
1379
+ return (this.db(db, dbOptions).collection(coll) as any).watch(
1380
+ pipeline,
1381
+ options
1382
+ );
1383
+ }
1384
+ throw new MongoshInternalError(
1385
+ 'Cannot call watch with defined collection but undefined db'
1386
+ );
1387
+ }
1388
+
1389
+ get driverMetadata(): ClientMetadata | undefined {
1390
+ return this.getTopology()?.clientMetadata;
1391
+ }
1392
+
1393
+ getRawClient(): MongoClient {
1394
+ return this.mongoClient;
1395
+ }
1396
+
1397
+ getURI(): string | undefined {
1398
+ return this.uri?.href;
1399
+ }
1400
+
1401
+ getFleOptions(): AutoEncryptionOptions | undefined {
1402
+ return this.currentClientOptions.autoEncryption;
1403
+ }
1404
+
1405
+ // Internal, only exposed for testing
1406
+ static processDriverOptions(
1407
+ currentProviderInstance: NodeDriverServiceProvider | null,
1408
+ uri: ConnectionString,
1409
+ opts: DevtoolsConnectOptions
1410
+ ): DevtoolsConnectOptions {
1411
+ const processedOptions = { ...DEFAULT_DRIVER_OPTIONS, ...opts };
1412
+
1413
+ if (currentProviderInstance?.currentClientOptions) {
1414
+ for (const key of ['productName', 'productDocsLink'] as const) {
1415
+ processedOptions[key] =
1416
+ currentProviderInstance.currentClientOptions[key];
1417
+ }
1418
+
1419
+ processedOptions.oidc ??= {};
1420
+ for (const key of [
1421
+ 'redirectURI',
1422
+ 'openBrowser',
1423
+ 'openBrowserTimeout',
1424
+ 'notifyDeviceFlow',
1425
+ 'allowedFlows',
1426
+ ] as const) {
1427
+ // Template IIFE so that TS understands that `key` on the left-hand and right-hand side match
1428
+ (<T extends keyof typeof processedOptions.oidc>(key: T) => {
1429
+ const value =
1430
+ currentProviderInstance.currentClientOptions.oidc?.[key];
1431
+ if (value) {
1432
+ processedOptions.oidc[key] = value;
1433
+ }
1434
+ })(key);
1435
+ }
1436
+ }
1437
+
1438
+ if (
1439
+ processedOptions.parentState ||
1440
+ processedOptions.parentHandle ||
1441
+ !currentProviderInstance
1442
+ ) {
1443
+ // Already set a parent state instance in the options explicitly,
1444
+ // or no state to inherit from a parent.
1445
+ return processedOptions;
1446
+ }
1447
+
1448
+ const currentOpts = currentProviderInstance.currentClientOptions;
1449
+ const currentUri = currentProviderInstance.uri;
1450
+ if (
1451
+ currentUri &&
1452
+ isDeepStrictEqual(
1453
+ normalizeEndpointAndAuthConfiguration(currentUri, currentOpts),
1454
+ normalizeEndpointAndAuthConfiguration(uri, processedOptions)
1455
+ )
1456
+ ) {
1457
+ if (currentOpts.parentState) {
1458
+ processedOptions.parentState = currentOpts.parentState;
1459
+ } else if (currentOpts.parentHandle) {
1460
+ processedOptions.parentHandle = currentOpts.parentHandle;
1461
+ }
1462
+ }
1463
+
1464
+ return processedOptions;
1465
+ }
1466
+
1467
+ // Internal, only exposed for testing
1468
+ processDriverOptions(
1469
+ uri: ConnectionString,
1470
+ opts: Partial<DevtoolsConnectOptions>
1471
+ ): DevtoolsConnectOptions {
1472
+ return NodeDriverServiceProvider.processDriverOptions(this, uri, {
1473
+ productName: this.currentClientOptions.productName,
1474
+ productDocsLink: this.currentClientOptions.productDocsLink,
1475
+ ...opts,
1476
+ });
1477
+ }
1478
+
1479
+ getSearchIndexes(
1480
+ database: string,
1481
+ collection: string,
1482
+ indexName?: string,
1483
+ // TODO(MONGOSH-1471): use ListSearchIndexesOptions once available
1484
+ options?: Document,
1485
+ dbOptions?: DbOptions
1486
+ ): Promise<Document[]> {
1487
+ const col = this.db(database, dbOptions).collection(collection);
1488
+ if (indexName === undefined) {
1489
+ return col.listSearchIndexes(options).toArray();
1490
+ } else {
1491
+ return col.listSearchIndexes(indexName, options).toArray();
1492
+ }
1493
+ }
1494
+
1495
+ createSearchIndexes(
1496
+ database: string,
1497
+ collection: string,
1498
+ specs: SearchIndexDescription[],
1499
+ dbOptions?: DbOptions
1500
+ ): Promise<string[]> {
1501
+ return this.db(database, dbOptions)
1502
+ .collection(collection)
1503
+ .createSearchIndexes(specs);
1504
+ }
1505
+
1506
+ dropSearchIndex(
1507
+ database: string,
1508
+ collection: string,
1509
+ indexName: string,
1510
+ dbOptions?: DbOptions
1511
+ ): Promise<void> {
1512
+ return this.db(database, dbOptions)
1513
+ .collection(collection)
1514
+ .dropSearchIndex(indexName);
1515
+ }
1516
+
1517
+ updateSearchIndex(
1518
+ database: string,
1519
+ collection: string,
1520
+ indexName: string,
1521
+ definition: SearchIndexDescription,
1522
+ dbOptions?: DbOptions
1523
+ ): Promise<void> {
1524
+ return this.db(database, dbOptions)
1525
+ .collection(collection)
1526
+ .updateSearchIndex(indexName, definition);
1527
+ }
1528
+
1529
+ createClientEncryption(
1530
+ options: ClientEncryptionOptions
1531
+ ): MongoCryptClientEncryption {
1532
+ return new ClientEncryption(this.mongoClient, options);
1533
+ }
1534
+ }
1535
+
1536
+ export { DevtoolsConnectOptions };