| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 | "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.MongoClient = exports.ServerApiVersion = void 0;const fs_1 = require("fs");const util_1 = require("util");const bson_1 = require("./bson");const change_stream_1 = require("./change_stream");const mongo_credentials_1 = require("./cmap/auth/mongo_credentials");const providers_1 = require("./cmap/auth/providers");const connection_string_1 = require("./connection_string");const constants_1 = require("./constants");const db_1 = require("./db");const error_1 = require("./error");const mongo_logger_1 = require("./mongo_logger");const mongo_types_1 = require("./mongo_types");const execute_operation_1 = require("./operations/execute_operation");const run_command_1 = require("./operations/run_command");const read_preference_1 = require("./read_preference");const server_selection_1 = require("./sdam/server_selection");const topology_1 = require("./sdam/topology");const sessions_1 = require("./sessions");const utils_1 = require("./utils");/** @public */exports.ServerApiVersion = Object.freeze({    v1: '1'});/** @internal */const kOptions = Symbol('options');/** * The **MongoClient** class is a class that allows for making Connections to MongoDB. * @public * * @remarks * The programmatically provided options take precedence over the URI options. * * @example * ```ts * import { MongoClient } from 'mongodb'; * * // Enable command monitoring for debugging * const client = new MongoClient('mongodb://localhost:27017', { monitorCommands: true }); * * client.on('commandStarted', started => console.log(started)); * client.db().collection('pets'); * await client.insertOne({ name: 'spot', kind: 'dog' }); * ``` */class MongoClient extends mongo_types_1.TypedEventEmitter {    constructor(url, options) {        super();        this[kOptions] = (0, connection_string_1.parseOptions)(url, this, options);        this.mongoLogger = new mongo_logger_1.MongoLogger(this[kOptions].mongoLoggerOptions);        // eslint-disable-next-line @typescript-eslint/no-this-alias        const client = this;        // The internal state        this.s = {            url,            bsonOptions: (0, bson_1.resolveBSONOptions)(this[kOptions]),            namespace: (0, utils_1.ns)('admin'),            hasBeenClosed: false,            sessionPool: new sessions_1.ServerSessionPool(this),            activeSessions: new Set(),            get options() {                return client[kOptions];            },            get readConcern() {                return client[kOptions].readConcern;            },            get writeConcern() {                return client[kOptions].writeConcern;            },            get readPreference() {                return client[kOptions].readPreference;            },            get isMongoClient() {                return true;            }        };    }    /** @see MongoOptions */    get options() {        return Object.freeze({ ...this[kOptions] });    }    get serverApi() {        return this[kOptions].serverApi && Object.freeze({ ...this[kOptions].serverApi });    }    /**     * Intended for APM use only     * @internal     */    get monitorCommands() {        return this[kOptions].monitorCommands;    }    set monitorCommands(value) {        this[kOptions].monitorCommands = value;    }    /** @internal */    get autoEncrypter() {        return this[kOptions].autoEncrypter;    }    get readConcern() {        return this.s.readConcern;    }    get writeConcern() {        return this.s.writeConcern;    }    get readPreference() {        return this.s.readPreference;    }    get bsonOptions() {        return this.s.bsonOptions;    }    /**     * Connect to MongoDB using a url     *     * @see docs.mongodb.org/manual/reference/connection-string/     */    async connect() {        if (this.connectionLock) {            return this.connectionLock;        }        try {            this.connectionLock = this._connect();            await this.connectionLock;        }        finally {            // release            this.connectionLock = undefined;        }        return this;    }    /**     * Create a topology to open the connection, must be locked to avoid topology leaks in concurrency scenario.     * Locking is enforced by the connect method.     *     * @internal     */    async _connect() {        if (this.topology && this.topology.isConnected()) {            return this;        }        const options = this[kOptions];        if (options.tls) {            if (typeof options.tlsCAFile === 'string') {                options.ca ??= await fs_1.promises.readFile(options.tlsCAFile);            }            if (typeof options.tlsCRLFile === 'string') {                options.crl ??= await fs_1.promises.readFile(options.tlsCRLFile);            }            if (typeof options.tlsCertificateKeyFile === 'string') {                if (!options.key || !options.cert) {                    const contents = await fs_1.promises.readFile(options.tlsCertificateKeyFile);                    options.key ??= contents;                    options.cert ??= contents;                }            }        }        if (typeof options.srvHost === 'string') {            const hosts = await (0, connection_string_1.resolveSRVRecord)(options);            for (const [index, host] of hosts.entries()) {                options.hosts[index] = host;            }        }        // It is important to perform validation of hosts AFTER SRV resolution, to check the real hostname,        // but BEFORE we even attempt connecting with a potentially not allowed hostname        if (options.credentials?.mechanism === providers_1.AuthMechanism.MONGODB_OIDC) {            const allowedHosts = options.credentials?.mechanismProperties?.ALLOWED_HOSTS || mongo_credentials_1.DEFAULT_ALLOWED_HOSTS;            const isServiceAuth = !!options.credentials?.mechanismProperties?.PROVIDER_NAME;            if (!isServiceAuth) {                for (const host of options.hosts) {                    if (!(0, utils_1.hostMatchesWildcards)(host.toHostPort().host, allowedHosts)) {                        throw new error_1.MongoInvalidArgumentError(`Host '${host}' is not valid for OIDC authentication with ALLOWED_HOSTS of '${allowedHosts.join(',')}'`);                    }                }            }        }        this.topology = new topology_1.Topology(this, options.hosts, options);        // Events can be emitted before initialization is complete so we have to        // save the reference to the topology on the client ASAP if the event handlers need to access it        this.topology.once(topology_1.Topology.OPEN, () => this.emit('open', this));        for (const event of constants_1.MONGO_CLIENT_EVENTS) {            this.topology.on(event, (...args) => this.emit(event, ...args));        }        const topologyConnect = async () => {            try {                await (0, util_1.promisify)(callback => this.topology?.connect(options, callback))();            }            catch (error) {                this.topology?.close({ force: true });                throw error;            }        };        if (this.autoEncrypter) {            await this.autoEncrypter?.init();            await topologyConnect();            await options.encrypter.connectInternalClient();        }        else {            await topologyConnect();        }        return this;    }    /**     * Close the client and its underlying connections     *     * @param force - Force close, emitting no events     */    async close(force = false) {        // There's no way to set hasBeenClosed back to false        Object.defineProperty(this.s, 'hasBeenClosed', {            value: true,            enumerable: true,            configurable: false,            writable: false        });        const activeSessionEnds = Array.from(this.s.activeSessions, session => session.endSession());        this.s.activeSessions.clear();        await Promise.all(activeSessionEnds);        if (this.topology == null) {            return;        }        // If we would attempt to select a server and get nothing back we short circuit        // to avoid the server selection timeout.        const selector = (0, server_selection_1.readPreferenceServerSelector)(read_preference_1.ReadPreference.primaryPreferred);        const topologyDescription = this.topology.description;        const serverDescriptions = Array.from(topologyDescription.servers.values());        const servers = selector(topologyDescription, serverDescriptions);        if (servers.length !== 0) {            const endSessions = Array.from(this.s.sessionPool.sessions, ({ id }) => id);            if (endSessions.length !== 0) {                await (0, execute_operation_1.executeOperation)(this, new run_command_1.RunAdminCommandOperation({ endSessions }, { readPreference: read_preference_1.ReadPreference.primaryPreferred, noResponse: true })).catch(() => null); // outcome does not matter;            }        }        // clear out references to old topology        const topology = this.topology;        this.topology = undefined;        await new Promise((resolve, reject) => {            topology.close({ force }, error => {                if (error)                    return reject(error);                const { encrypter } = this[kOptions];                if (encrypter) {                    return encrypter.closeCallback(this, force, error => {                        if (error)                            return reject(error);                        resolve();                    });                }                resolve();            });        });    }    /**     * Create a new Db instance sharing the current socket connections.     *     * @param dbName - The name of the database we want to use. If not provided, use database name from connection string.     * @param options - Optional settings for Db construction     */    db(dbName, options) {        options = options ?? {};        // Default to db from connection string if not provided        if (!dbName) {            dbName = this.options.dbName;        }        // Copy the options and add out internal override of the not shared flag        const finalOptions = Object.assign({}, this[kOptions], options);        // Return the db object        const db = new db_1.Db(this, dbName, finalOptions);        // Return the database        return db;    }    /**     * Connect to MongoDB using a url     *     * @remarks     * The programmatically provided options take precedence over the URI options.     *     * @see https://www.mongodb.com/docs/manual/reference/connection-string/     */    static async connect(url, options) {        const client = new this(url, options);        return client.connect();    }    /**     * Creates a new ClientSession. When using the returned session in an operation     * a corresponding ServerSession will be created.     *     * @remarks     * A ClientSession instance may only be passed to operations being performed on the same     * MongoClient it was started from.     */    startSession(options) {        const session = new sessions_1.ClientSession(this, this.s.sessionPool, { explicit: true, ...options }, this[kOptions]);        this.s.activeSessions.add(session);        session.once('ended', () => {            this.s.activeSessions.delete(session);        });        return session;    }    async withSession(optionsOrExecutor, executor) {        const options = {            // Always define an owner            owner: Symbol(),            // If it's an object inherit the options            ...(typeof optionsOrExecutor === 'object' ? optionsOrExecutor : {})        };        const withSessionCallback = typeof optionsOrExecutor === 'function' ? optionsOrExecutor : executor;        if (withSessionCallback == null) {            throw new error_1.MongoInvalidArgumentError('Missing required callback parameter');        }        const session = this.startSession(options);        try {            return await withSessionCallback(session);        }        finally {            try {                await session.endSession();            }            catch {                // We are not concerned with errors from endSession()            }        }    }    /**     * Create a new Change Stream, watching for new changes (insertions, updates,     * replacements, deletions, and invalidations) in this cluster. Will ignore all     * changes to system collections, as well as the local, admin, and config databases.     *     * @remarks     * watch() accepts two generic arguments for distinct use cases:     * - The first is to provide the schema that may be defined for all the data within the current cluster     * - The second is to override the shape of the change stream document entirely, if it is not provided the type will default to ChangeStreamDocument of the first argument     *     * @param pipeline - An array of {@link https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.     * @param options - Optional settings for the command     * @typeParam TSchema - Type of the data being detected by the change stream     * @typeParam TChange - Type of the whole change stream document emitted     */    watch(pipeline = [], options = {}) {        // Allow optionally not specifying a pipeline        if (!Array.isArray(pipeline)) {            options = pipeline;            pipeline = [];        }        return new change_stream_1.ChangeStream(this, pipeline, (0, utils_1.resolveOptions)(this, options));    }}exports.MongoClient = MongoClient;//# sourceMappingURL=mongo_client.js.map
 |