"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryConfig = exports.SessionConfig = exports.routing = exports.WRITE = exports.READ = exports.Driver = void 0;
var bookmarks_1 = require("./internal/bookmarks");
var configured_custom_resolver_1 = __importDefault(require("./internal/resolver/configured-custom-resolver"));
var constants_1 = require("./internal/constants");
var logger_1 = require("./internal/logger");
var session_1 = __importDefault(require("./session"));
var util_1 = require("./internal/util");
var bookmark_manager_1 = require("./bookmark-manager");
var result_transformers_1 = __importDefault(require("./result-transformers"));
var query_executor_1 = __importDefault(require("./internal/query-executor"));
var error_1 = require("./error");
var homedb_cache_1 = __importDefault(require("./internal/homedb-cache"));
var auth_util_1 = require("./internal/auth-util");
var DEFAULT_MAX_CONNECTION_LIFETIME = 60 * 60 * 1000; // 1 hour
/**
 * The default record fetch size. This is used in Bolt V4 protocol to pull query execution result in batches.
 * @type {number}
 */
var DEFAULT_FETCH_SIZE = 1000;
/**
 * The maximum number of entries allowed in the home database cache before pruning.
 */
var HOMEDB_CACHE_MAX_SIZE = 10000;
/**
 * Constant that represents read session access mode.
 * Should be used like this: `driver.session({ defaultAccessMode: neo4j.session.READ })`.
 * @type {string}
 */
var READ = constants_1.ACCESS_MODE_READ;
exports.READ = READ;
/**
 * Constant that represents write session access mode.
 * Should be used like this: `driver.session({ defaultAccessMode: neo4j.session.WRITE })`.
 * @type {string}
 */
var WRITE = constants_1.ACCESS_MODE_WRITE;
exports.WRITE = WRITE;
var idGenerator = 0;
/**
 * The session configuration
 *
 * @interface
 */
var SessionConfig = /** @class */ (function () {
    /**
     * @constructor
     * @private
     */
    function SessionConfig() {
        /**
         * The access mode of this session, allowed values are {@link READ} and {@link WRITE}.
         * **Default**: {@link WRITE}
         * @type {string}
         */
        this.defaultAccessMode = WRITE;
        /**
         * The initial reference or references to some previous
         * transactions. Value is optional and absence indicates that that the bookmarks do not exist or are unknown.
         * @type {string|string[]|undefined}
         */
        this.bookmarks = [];
        /**
         * The database this session will operate on.
         *
         * This option has no explicit value by default, but it is recommended to set
         * one if the target database is known in advance. This has the benefit of
         * ensuring a consistent target database name throughout the session in a
         * straightforward way and potentially simplifies driver logic as well as
         * reduces network communication resulting in better performance.
         *
         * Usage of Cypher clauses like USE is not a replacement for this option.
         * The driver does not parse any Cypher.
         *
         * When no explicit name is set, the driver behavior depends on the connection
         * URI scheme supplied to the driver on instantiation and Bolt protocol
         * version.
         *
         * Specifically, the following applies:
         *
         * - **bolt schemes** - queries are dispatched to the server for execution
         *   without explicit database name supplied, meaning that the target database
         *   name for query execution is determined by the server. It is important to
         *   note that the target database may change (even within the same session),
         *   for instance if the user's home database is changed on the server.
         *
         * - **neo4j schemes** - providing that Bolt protocol version 4.4, which was
         *   introduced with Neo4j server 4.4, or above is available, the driver
         *   fetches the user's home database name from the server on first query
         *   execution within the session and uses the fetched database name
         *   explicitly for all queries executed within the session. This ensures that
         *   the database name remains consistent within the given session. For
         *   instance, if the user's home database name is 'movies' and the server
         *   supplies it to the driver upon database name fetching for the session,
         *   all queries within that session are executed with the explicit database
         *   name 'movies' supplied. Any change to the user’s home database is
         *   reflected only in sessions created after such change takes effect. This
         *   behavior requires additional network communication. In clustered
         *   environments, it is strongly recommended to avoid a single point of
         *   failure. For instance, by ensuring that the connection URI resolves to
         *   multiple endpoints. For older Bolt protocol versions the behavior is the
         *   same as described for the **bolt schemes** above.
         *
         * @type {string|undefined}
         */
        this.database = '';
        /**
         * The username which the user wants to impersonate for the duration of the session.
         *
         * @type {string|undefined}
         */
        this.impersonatedUser = undefined;
        /**
         * The {@link AuthToken} which will be used for the duration of the session.
         *
         * By default, the session will use connections authenticated with the {@link AuthToken} configured on
         * driver creation. This configuration allows switching user and/or authorization information for the
         * session lifetime.
         *
         * **Warning**: This option is only available when the driver is connected to Neo4j Database servers
         * which supports Bolt 5.1 or newer.
         *
         * @type {AuthToken|undefined}
         * @see {@link driver}
         */
        this.auth = undefined;
        /**
         * The record fetch size of each batch of this session.
         *
         * Use {@link FETCH_ALL} to always pull all records in one batch. This will override the config value set on driver config.
         *
         * @type {number|undefined}
         */
        this.fetchSize = undefined;
        /**
         * Configure a BookmarkManager for the session to use
         *
         * A BookmarkManager is a piece of software responsible for keeping casual consistency between different sessions by sharing bookmarks
         * between the them.
         * Enabling it is done by supplying an BookmarkManager implementation instance to this param.
         * A default implementation could be acquired by calling the factory function {@link bookmarkManager}.
         *
         * **Warning**: Sharing the same BookmarkManager instance across multiple sessions can have a negative impact
         * on performance since all the queries will wait for the latest changes being propagated across the cluster.
         * For keeping consistency between a group of queries, use {@link Session} for grouping them.
         * For keeping consistency between a group of sessions, use {@link BookmarkManager} instance for grouping them.
         *
         * @example
         * const bookmarkManager = neo4j.bookmarkManager()
         * const linkedSession1 = driver.session({ database:'neo4j', bookmarkManager })
         * const linkedSession2 = driver.session({ database:'neo4j', bookmarkManager })
         * const unlinkedSession = driver.session({ database:'neo4j' })
         *
         * // Creating Driver User
         * const createUserQueryResult = await linkedSession1.run('CREATE (p:Person {name: $name})', { name: 'Driver User'})
         *
         * // Reading Driver User will *NOT* wait of the changes being propagated to the server before RUN the query
         * // So the 'Driver User' person might not exist in the Result
         * const unlinkedReadResult = await unlinkedSession.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
         *
         * // Reading Driver User will wait of the changes being propagated to the server before RUN the query
         * // So the 'Driver User' person should exist in the Result, unless deleted.
         * const linkedResult = await linkedSession2.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
         *
         * await linkedSession1.close()
         * await linkedSession2.close()
         * await unlinkedSession.close()
         *
         * @type {BookmarkManager|undefined}
         * @since 5.0
         */
        this.bookmarkManager = undefined;
        /**
         * Configure filter for {@link Notification} objects returned in {@link ResultSummary#notifications}.
         *
         * This configuration enables filter notifications by:
         *
         * * the minimum severity level ({@link NotificationFilterMinimumSeverityLevel})
         * * disabling notification categories ({@link NotificationFilterDisabledCategory})
         *
         *
         * Disabling notifications can be done by defining the minimum severity level to 'OFF'.
         * At driver level, when omitted, uses the server's default.
         * At session level, when omitted, defaults to what filters have been configured at driver level.
         *
         * Disabling categories or severities allows the server to skip analysis for those, which can speed up query
         * execution.
         *
         * @example
         * // enabling warning notification, but disabling `HINT` and `DEPRECATION` notifications.
         * const session = driver.session({
         *     database: 'neo4j',
         *     notificationFilter: {
         *         minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING, // or 'WARNING
         *         disabledCategories: [
         *             neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT'
         *             neo4j.notificationFilterDisabledCategory.DEPRECATION // or 'DEPRECATION'
         *        ]
         *     }
         * })
         *
         * @example
         * // disabling notifications for a session
         * const session = driver.session({
         *     database: 'neo4j',
         *     notificationFilter: {
         *         minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.OFF // or 'OFF'
         *     }
         * })
         *
         * @example
         * // using default values configured in the driver
         * const sessionWithDefaultValues = driver.session({ database: 'neo4j' })
         * // or driver.session({ database: 'neo4j', notificationFilter: undefined })
         *
         * // using default minimum severity level, but disabling 'HINT' and 'UNRECOGNIZED'
         * // notification categories
         * const sessionWithDefaultSeverityLevel = driver.session({
         *     database: 'neo4j',
         *     notificationFilter: {
         *         disabledCategories: [
         *             neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT'
         *             neo4j.notificationFilterDisabledCategory.UNRECOGNIZED // or 'UNRECOGNIZED'
         *        ]
         *     }
         * })
         *
         * // using default disabled categories, but configuring minimum severity level to 'WARNING'
         * const sessionWithDefaultSeverityLevel = driver.session({
         *     database: 'neo4j',
         *     notificationFilter: {
         *         minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING // or 'WARNING'
         *     }
         * })
         *
         * @type {NotificationFilter|undefined}
         * @since 5.7
         */
        this.notificationFilter = undefined;
    }
    return SessionConfig;
}());
exports.SessionConfig = SessionConfig;
var ROUTING_WRITE = 'WRITE';
var ROUTING_READ = 'READ';
/**
 * @typedef {'WRITE'|'READ'} RoutingControl
 */
/**
 * Constants that represents routing modes.
 *
 * @example
 * driver.executeQuery("<QUERY>", <PARAMETERS>, { routing: neo4j.routing.WRITE })
 */
var routing = {
    WRITE: ROUTING_WRITE,
    READ: ROUTING_READ
};
exports.routing = routing;
Object.freeze(routing);
/**
 * The query configuration
 * @interface
 */
var QueryConfig = /** @class */ (function () {
    /**
     * @constructor
     * @private
     */
    function QueryConfig() {
        /**
         * Define the type of cluster member the query will be routed to.
         *
         * @type {RoutingControl}
         */
        this.routing = routing.WRITE;
        /**
         * Define the transformation will be applied to the Result before return from the
         * query method.
         *
         * @type {ResultTransformer}
         * @see {@link resultTransformers} for provided implementations.
         */
        this.resultTransformer = undefined;
        /**
         * The database this session will operate on.
         *
         * @type {string|undefined}
         */
        this.database = '';
        /**
         * The username which the user wants to impersonate for the duration of the query.
         *
         * @type {string|undefined}
         */
        this.impersonatedUser = undefined;
        /**
         * Configure a BookmarkManager for the session to use
         *
         * A BookmarkManager is a piece of software responsible for keeping casual consistency between different pieces of work by sharing bookmarks
         * between the them.
         *
         * By default, it uses the driver's non mutable driver level bookmark manager. See, {@link Driver.executeQueryBookmarkManager}
         *
         * Can be set to null to disable causal chaining.
         * @type {BookmarkManager|undefined|null}
         */
        this.bookmarkManager = undefined;
        /**
         * Configuration for all transactions started to execute the query.
         *
         * @type {TransactionConfig|undefined}
         *
         */
        this.transactionConfig = undefined;
        /**
         * The {@link AuthToken} which will be used for executing the query.
         *
         * By default, the query executor will use connections authenticated with the {@link AuthToken} configured on
         * driver creation. This configuration allows switching user and/or authorization information for the
         * underlying transaction's lifetime.
         *
         * **Warning**: This option is only available when the driver is connected to Neo4j Database servers
         * which support Bolt 5.1 or newer.
         *
         * @type {AuthToken|undefined}
         * @see {@link driver}
         */
        this.auth = undefined;
        /**
         * The {@link AbortSignal} for aborting query execution.
         *
         * When aborted, the signal triggers the result consumption cancelation and
         * transactions are reset. However, due to race conditions,
         * there is no guarantee the transaction will be rolled back.
         * Equivalent to {@link Session.close}
         *
         * **Warning**: This option is only available in runtime which supports AbortSignal.addEventListener.
         *
         * @since 5.22.0
         * @type {AbortSignal|undefined}
         * @experimental
         * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
         */
        this.signal = undefined;
    }
    return QueryConfig;
}());
exports.QueryConfig = QueryConfig;
/**
 * A driver maintains one or more {@link Session}s with a remote
 * Neo4j instance. Through the {@link Session}s you can send queries
 * and retrieve results from the database.
 *
 * Drivers are reasonably expensive to create - you should strive to keep one
 * driver instance around per Neo4j Instance you connect to.
 *
 * @access public
 */
var Driver = /** @class */ (function () {
    /**
     * You should not be calling this directly, instead use {@link driver}.
     * @constructor
     * @protected
     * @param {Object} meta Metainformation about the driver
     * @param {Object} config
     * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectionProvider Creates the connection provider
     * @param {function(args): Session } createSession Creates the a session
    */
    function Driver(meta, config, createConnectionProvider, createSession, createQueryExecutor) {
        if (config === void 0) { config = {}; }
        if (createSession === void 0) { createSession = function (args) { return new session_1.default(args); }; }
        if (createQueryExecutor === void 0) { createQueryExecutor = function (createSession) { return new query_executor_1.default(createSession); }; }
        sanitizeConfig(config);
        var log = logger_1.Logger.create(config);
        validateConfig(config, log);
        this._id = idGenerator++;
        this._meta = meta;
        this._config = config;
        this._log = log;
        this._createConnectionProvider = createConnectionProvider;
        this._createSession = createSession;
        this._defaultExecuteQueryBookmarkManager = (0, bookmark_manager_1.bookmarkManager)();
        this._queryExecutor = createQueryExecutor(this.session.bind(this));
        /**
         * Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
         * @type {ConnectionProvider}
         * @protected
         */
        this._connectionProvider = null;
        /**
         * @private
         */
        this.homeDatabaseCache = new homedb_cache_1.default(HOMEDB_CACHE_MAX_SIZE);
        this._afterConstruction();
    }
    Object.defineProperty(Driver.prototype, "executeQueryBookmarkManager", {
        /**
         * The bookmark managed used by {@link Driver.executeQuery}
         *
         * @type {BookmarkManager}
         */
        get: function () {
            return this._defaultExecuteQueryBookmarkManager;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Executes a query in a retriable context and returns a {@link EagerResult}.
     *
     * This method is a shortcut for a {@link Session#executeRead} and {@link Session#executeWrite}.
     *
     * NOTE: Because it is an explicit transaction from the server point of view, Cypher queries using
     * "CALL {} IN TRANSACTIONS" or the older "USING PERIODIC COMMIT" construct will not work (call
     * {@link Session#run} for these).
     *
     * @example
     * // Run a simple write query
     * const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
     *
     * @example
     * // Run a read query
     * const { keys, records, summary } = await driver.executeQuery(
     *    'MATCH (p:Person{ name: $name }) RETURN p',
     *    { name: 'Person1'},
     *    { routing: neo4j.routing.READ})
     *
     * @example
     * // Run a read query returning a Person Nodes per elementId
     * const peopleMappedById = await driver.executeQuery(
     *    'MATCH (p:Person{ name: $name }) RETURN p',
     *    { name: 'Person1'},
     *    {
     *      resultTransformer: neo4j.resultTransformers.mappedResultTransformer({
     *        map(record) {
     *          const p = record.get('p')
     *          return [p.elementId, p]
     *        },
     *        collect(elementIdPersonPairArray) {
     *          return new Map(elementIdPersonPairArray)
     *        }
     *      })
     *    }
     * )
     *
     * const person = peopleMappedById.get("<ELEMENT_ID>")
     *
     * @example
     * // these lines
     * const transformedResult = await driver.executeQuery(
     *    "<QUERY>",
     *    <PARAMETERS>,
     *    {
     *       routing: neo4j.routing.WRITE,
     *       resultTransformer: transformer,
     *       database: "<DATABASE>",
     *       impersonatedUser: "<USER>",
     *       bookmarkManager: bookmarkManager
     *    })
     * // are equivalent to those
     * const session = driver.session({
     *    database: "<DATABASE>",
     *    impersonatedUser: "<USER>",
     *    bookmarkManager: bookmarkManager
     * })
     *
     * try {
     *    const transformedResult = await session.executeWrite(tx => {
     *        const result = tx.run("<QUERY>", <PARAMETERS>)
     *        return transformer(result)
     *    })
     * } finally {
     *    await session.close()
     * }
     *
     * @public
     * @param {string | {text: string, parameters?: object}} query - Cypher query to execute
     * @param {Object} parameters - Map with parameters to use in the query
     * @param {QueryConfig<T>} config - The query configuration
     * @returns {Promise<T>}
     *
     * @see {@link resultTransformers} for provided result transformers.
     */
    Driver.prototype.executeQuery = function (query_1, parameters_1) {
        return __awaiter(this, arguments, void 0, function (query, parameters, config) {
            var bookmarkManager, resultTransformer, routingConfig;
            var _a, _b, _c;
            if (config === void 0) { config = {}; }
            return __generator(this, function (_d) {
                switch (_d.label) {
                    case 0:
                        bookmarkManager = config.bookmarkManager === null ? undefined : ((_a = config.bookmarkManager) !== null && _a !== void 0 ? _a : this.executeQueryBookmarkManager);
                        resultTransformer = ((_b = config.resultTransformer) !== null && _b !== void 0 ? _b : result_transformers_1.default.eagerResultTransformer());
                        routingConfig = (_c = config.routing) !== null && _c !== void 0 ? _c : routing.WRITE;
                        if (routingConfig !== routing.READ && routingConfig !== routing.WRITE) {
                            throw (0, error_1.newError)("Illegal query routing config: \"".concat(routingConfig, "\""));
                        }
                        return [4 /*yield*/, this._queryExecutor.execute({
                                resultTransformer: resultTransformer,
                                bookmarkManager: bookmarkManager,
                                routing: routingConfig,
                                database: config.database,
                                impersonatedUser: config.impersonatedUser,
                                transactionConfig: config.transactionConfig,
                                auth: config.auth,
                                signal: config.signal
                            }, query, parameters)];
                    case 1: return [2 /*return*/, _d.sent()];
                }
            });
        });
    };
    /**
     * Verifies connectivity of this driver by trying to open a connection with the provided driver options.
     *
     * @deprecated This return of this method will change in 6.0.0 to not async return the {@link ServerInfo} and
     * async return {@link void} instead. If you need to use the server info, use {@link getServerInfo} instead.
     *
     * @public
     * @param {Object} param - The object parameter
     * @param {string} param.database - The target database to verify connectivity for.
     * @returns {Promise<ServerInfo>} promise resolved with server info or rejected with error.
     */
    Driver.prototype.verifyConnectivity = function (_a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.database, database = _c === void 0 ? '' : _c;
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.verifyConnectivityAndGetServerInfo({ database: database, accessMode: READ });
    };
    /**
     * This method verifies the authorization credentials work by trying to acquire a connection
     * to one of the servers with the given credentials.
     *
     * @param {object} param - object parameter
     * @property {AuthToken} param.auth - the target auth for the to-be-acquired connection
     * @property {string} param.database - the target database for the to-be-acquired connection
     *
     * @returns {Promise<boolean>} promise resolved with true if succeed, false if failed with
     *  authentication issue and rejected with error if non-authentication error happens.
     */
    Driver.prototype.verifyAuthentication = function () {
        return __awaiter(this, arguments, void 0, function (_a) {
            var connectionProvider;
            var _b = _a === void 0 ? {} : _a, database = _b.database, auth = _b.auth;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        connectionProvider = this._getOrCreateConnectionProvider();
                        return [4 /*yield*/, connectionProvider.verifyAuthentication({
                                database: database !== null && database !== void 0 ? database : 'system',
                                auth: auth,
                                accessMode: READ
                            })];
                    case 1: return [2 /*return*/, _c.sent()];
                }
            });
        });
    };
    /**
     * Get ServerInfo for the giver database.
     *
     * @param {Object} param - The object parameter
     * @param {string} param.database - The target database to verify connectivity for.
     * @returns {Promise<ServerInfo>} promise resolved with the ServerInfo or rejected with error.
     */
    Driver.prototype.getServerInfo = function (_a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.database, database = _c === void 0 ? '' : _c;
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.verifyConnectivityAndGetServerInfo({ database: database, accessMode: READ });
    };
    /**
     * Returns whether the server supports multi database capabilities based on the protocol
     * version negotiated via handshake.
     *
     * Note that this function call _always_ causes a round-trip to the server.
     *
     * @returns {Promise<boolean>} promise resolved with a boolean or rejected with error.
     */
    Driver.prototype.supportsMultiDb = function () {
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.supportsMultiDb();
    };
    /**
     * Returns whether the server supports transaction config capabilities based on the protocol
     * version negotiated via handshake.
     *
     * Note that this function call _always_ causes a round-trip to the server.
     *
     * @returns {Promise<boolean>} promise resolved with a boolean or rejected with error.
     */
    Driver.prototype.supportsTransactionConfig = function () {
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.supportsTransactionConfig();
    };
    /**
     * Returns whether the server supports user impersonation capabilities based on the protocol
     * version negotiated via handshake.
     *
     * Note that this function call _always_ causes a round-trip to the server.
     *
     * @returns {Promise<boolean>} promise resolved with a boolean or rejected with error.
     */
    Driver.prototype.supportsUserImpersonation = function () {
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.supportsUserImpersonation();
    };
    /**
     * Returns whether the driver session re-auth functionality capabilities based on the protocol
     * version negotiated via handshake.
     *
     * Note that this function call _always_ causes a round-trip to the server.
     *
     * @returns {Promise<boolean>} promise resolved with a boolean or rejected with error.
     */
    Driver.prototype.supportsSessionAuth = function () {
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.supportsSessionAuth();
    };
    /**
     * Returns the protocol version negotiated via handshake.
     *
     * Note that this function call _always_ causes a round-trip to the server.
     *
     * @returns {Promise<number>} the protocol version negotiated via handshake.
     * @throws {Error} When protocol negotiation fails
     */
    Driver.prototype.getNegotiatedProtocolVersion = function () {
        var connectionProvider = this._getOrCreateConnectionProvider();
        return connectionProvider.getNegotiatedProtocolVersion();
    };
    /**
     * Returns boolean to indicate if driver has been configured with encryption enabled.
     *
     * @returns {boolean}
     */
    Driver.prototype.isEncrypted = function () {
        return this._isEncrypted();
    };
    /**
     * @protected
     * @returns {boolean}
     */
    Driver.prototype._supportsRouting = function () {
        return this._meta.routing;
    };
    /**
     * Returns boolean to indicate if driver has been configured with encryption enabled.
     *
     * @protected
     * @returns {boolean}
     */
    Driver.prototype._isEncrypted = function () {
        return this._config.encrypted === util_1.ENCRYPTION_ON || this._config.encrypted === true;
    };
    /**
     * Returns the configured trust strategy that the driver has been configured with.
     *
     * @protected
     * @returns {TrustStrategy}
     */
    Driver.prototype._getTrust = function () {
        return this._config.trust;
    };
    /**
     * Acquire a session to communicate with the database. The session will
     * borrow connections from the underlying connection pool as required and
     * should be considered lightweight and disposable.
     *
     * This comes with some responsibility - make sure you always call
     * {@link close} when you are done using a session, and likewise,
     * make sure you don't close your session before you are done using it. Once
     * it is closed, the underlying connection will be released to the connection
     * pool and made available for others to use.
     *
     * @public
     * @param {SessionConfig} param - The session configuration
     * @return {Session} new session.
     */
    Driver.prototype.session = function (_a) {
        var _b = _a === void 0 ? {} : _a, _c = _b.defaultAccessMode, defaultAccessMode = _c === void 0 ? WRITE : _c, bookmarkOrBookmarks = _b.bookmarks, _d = _b.database, database = _d === void 0 ? '' : _d, impersonatedUser = _b.impersonatedUser, fetchSize = _b.fetchSize, bookmarkManager = _b.bookmarkManager, notificationFilter = _b.notificationFilter, auth = _b.auth;
        return this._newSession({
            defaultAccessMode: defaultAccessMode,
            bookmarkOrBookmarks: bookmarkOrBookmarks,
            database: database,
            reactive: false,
            impersonatedUser: impersonatedUser,
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            fetchSize: validateFetchSizeValue(fetchSize, this._config.fetchSize),
            bookmarkManager: bookmarkManager,
            notificationFilter: notificationFilter,
            auth: auth
        });
    };
    /**
     * Close all open sessions and other associated resources. You should
     * make sure to use this when you are done with this driver instance.
     *
     * This will interrupt any running connections.
     * Make sure you are done using the driver object and any resources
     * spawned from it (such as sessions or transactions) while calling
     * this method. Failing to do so will result in unspecified behavior.
     *
     * @public
     * @return {Promise<void>} promise resolved when the driver is closed.
     */
    Driver.prototype.close = function () {
        this._log.info("Driver ".concat(this._id, " closing"));
        if (this._connectionProvider != null) {
            return this._connectionProvider.close();
        }
        return Promise.resolve();
    };
    // eslint-disable-next-line
    // @ts-ignore
    Driver.prototype[Symbol.asyncDispose] = function () {
        return this.close();
    };
    /**
     * @protected
     * @returns {void}
     */
    Driver.prototype._afterConstruction = function () {
        this._log.info("".concat(this._meta.typename, " driver ").concat(this._id, " created for server address ").concat(this._meta.address.toString()));
    };
    Driver.prototype._homeDatabaseCallback = function (cacheKey, database) {
        this.homeDatabaseCache.set(cacheKey, database);
    };
    /**
     * @private
     */
    Driver.prototype._newSession = function (_a) {
        var defaultAccessMode = _a.defaultAccessMode, bookmarkOrBookmarks = _a.bookmarkOrBookmarks, database = _a.database, reactive = _a.reactive, impersonatedUser = _a.impersonatedUser, fetchSize = _a.fetchSize, bookmarkManager = _a.bookmarkManager, notificationFilter = _a.notificationFilter, auth = _a.auth;
        var sessionMode = session_1.default._validateSessionMode(defaultAccessMode);
        var connectionProvider = this._getOrCreateConnectionProvider();
        // eslint-disable-next-line
        var cachedHomeDatabase = this.homeDatabaseCache.get((0, auth_util_1.cacheKey)(auth, impersonatedUser));
        var homeDatabaseCallback = this._homeDatabaseCallback.bind(this);
        var bookmarks = bookmarkOrBookmarks != null
            ? new bookmarks_1.Bookmarks(bookmarkOrBookmarks)
            : bookmarks_1.Bookmarks.empty();
        return this._createSession({
            mode: sessionMode,
            database: database !== null && database !== void 0 ? database : '',
            connectionProvider: connectionProvider,
            bookmarks: bookmarks,
            config: __assign({ cachedHomeDatabase: cachedHomeDatabase, routingDriver: this._supportsRouting() }, this._config),
            reactive: reactive,
            impersonatedUser: impersonatedUser,
            fetchSize: fetchSize,
            bookmarkManager: bookmarkManager,
            notificationFilter: notificationFilter,
            auth: auth,
            log: this._log,
            homeDatabaseCallback: homeDatabaseCallback
        });
    };
    /**
     * @private
     */
    Driver.prototype._getOrCreateConnectionProvider = function () {
        if (this._connectionProvider == null) {
            this._connectionProvider = this._createConnectionProvider(this._id, this._config, this._log, createHostNameResolver(this._config));
        }
        return this._connectionProvider;
    };
    return Driver;
}());
exports.Driver = Driver;
/**
 * @private
 * @returns {Object} the given config.
 */
function validateConfig(config, log) {
    var _a, _b;
    var resolver = config.resolver;
    if (resolver !== null && resolver !== undefined && typeof resolver !== 'function') {
        throw new TypeError("Configured resolver should be a function. Got: ".concat(typeof resolver));
    }
    if (config.connectionAcquisitionTimeout < config.connectionTimeout) {
        log.warn('Configuration for "connectionAcquisitionTimeout" should be greater than ' +
            'or equal to "connectionTimeout". Otherwise, the connection acquisition ' +
            'timeout will take precedence for over the connection timeout in scenarios ' +
            'where a new connection is created while it is acquired');
    }
    if (((_a = config.notificationFilter) === null || _a === void 0 ? void 0 : _a.disabledCategories) != null && ((_b = config.notificationFilter) === null || _b === void 0 ? void 0 : _b.disabledClassifications) != null) {
        throw new Error('The notificationFilter can\'t have both "disabledCategories" and  "disabledClassifications" configured at the same time.');
    }
    return config;
}
/**
 * @private
 * @returns {void}
 */
function sanitizeConfig(config) {
    config.maxConnectionLifetime = sanitizeIntValue(config.maxConnectionLifetime, DEFAULT_MAX_CONNECTION_LIFETIME);
    config.maxConnectionPoolSize = sanitizeIntValue(config.maxConnectionPoolSize, constants_1.DEFAULT_POOL_MAX_SIZE);
    config.connectionAcquisitionTimeout = sanitizeIntValue(config.connectionAcquisitionTimeout, constants_1.DEFAULT_POOL_ACQUISITION_TIMEOUT);
    config.fetchSize = validateFetchSizeValue(config.fetchSize, DEFAULT_FETCH_SIZE);
    config.connectionTimeout = extractConnectionTimeout(config);
    config.connectionLivenessCheckTimeout =
        validateConnectionLivenessCheckTimeoutSizeValue(config.connectionLivenessCheckTimeout);
}
/**
 * @private
 * @returns {number}
 */
function sanitizeIntValue(rawValue, defaultWhenAbsent) {
    var sanitizedValue = parseInt(rawValue, 10);
    if (sanitizedValue > 0 || sanitizedValue === 0) {
        return sanitizedValue;
    }
    else if (sanitizedValue < 0) {
        return Number.MAX_SAFE_INTEGER;
    }
    else {
        return defaultWhenAbsent;
    }
}
/**
 * @private
 */
function validateFetchSizeValue(rawValue, defaultWhenAbsent) {
    var fetchSize = parseInt(rawValue, 10);
    if (fetchSize > 0 || fetchSize === constants_1.FETCH_ALL) {
        return fetchSize;
    }
    else if (fetchSize === 0 || fetchSize < 0) {
        throw new Error("The fetch size can only be a positive value or ".concat(constants_1.FETCH_ALL, " for ALL. However fetchSize = ").concat(fetchSize));
    }
    else {
        return defaultWhenAbsent;
    }
}
/**
 * @private
 */
function extractConnectionTimeout(config) {
    var configuredTimeout = parseInt(config.connectionTimeout, 10);
    if (configuredTimeout === 0) {
        // timeout explicitly configured to 0
        return null;
    }
    else if (!isNaN(configuredTimeout) && configuredTimeout < 0) {
        // timeout explicitly configured to a negative value
        return null;
    }
    else if (isNaN(configuredTimeout)) {
        // timeout not configured, use default value
        return constants_1.DEFAULT_CONNECTION_TIMEOUT_MILLIS;
    }
    else {
        // timeout configured, use the provided value
        return configuredTimeout;
    }
}
/**
 * @private
 */
function validateConnectionLivenessCheckTimeoutSizeValue(rawValue) {
    if (rawValue == null) {
        return undefined;
    }
    var connectionLivenessCheckTimeout = parseInt(rawValue, 10);
    if (connectionLivenessCheckTimeout < 0 || Number.isNaN(connectionLivenessCheckTimeout)) {
        throw new Error("The connectionLivenessCheckTimeout can only be a positive value or 0 for always. However connectionLivenessCheckTimeout = ".concat(connectionLivenessCheckTimeout));
    }
    return connectionLivenessCheckTimeout;
}
/**
 * @private
 * @returns {ConfiguredCustomResolver} new custom resolver that wraps the passed-in resolver function.
 *              If resolved function is not specified, it defaults to an identity resolver.
 */
function createHostNameResolver(config) {
    return new configured_custom_resolver_1.default(config.resolver);
}
exports.default = Driver;
