"use strict";
/**
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
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 __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/promise-function-async */
var observers_1 = require("./internal/observers");
var util_1 = require("./internal/util");
var constants_1 = require("./internal/constants");
var error_1 = require("./error");
var result_1 = __importDefault(require("./result"));
var connection_holder_1 = require("./internal/connection-holder");
var transaction_executor_1 = require("./internal/transaction-executor");
var bookmarks_1 = require("./internal/bookmarks");
var tx_config_1 = require("./internal/tx-config");
var transaction_promise_1 = __importDefault(require("./transaction-promise"));
var transaction_managed_1 = __importDefault(require("./transaction-managed"));
var auth_util_1 = require("./internal/auth-util");
/**
 * A Session instance is used for handling the connection and
 * sending queries through the connection.
 * In a single session, multiple queries will be executed serially.
 * In order to execute parallel queries, multiple sessions are required.
 * @access public
 */
var Session = /** @class */ (function () {
    /**
     * @constructor
     * @protected
     * @param {Object} args
     * @param {string} args.mode the default access mode for this session.
     * @param {ConnectionProvider} args.connectionProvider - The connection provider to acquire connections from.
     * @param {Bookmarks} args.bookmarks - The initial bookmarks for this session.
     * @param {string} args.database the database name
     * @param {Object} args.config={} - This driver configuration.
     * @param {boolean} args.reactive - Whether this session should create reactive streams
     * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch
     * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session.
     * @param {BookmarkManager} args.bookmarkManager - The bookmark manager used for this session.
     * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session.
     * @param {AuthToken} args.auth - the target auth for the to-be-acquired connection
     * @param {Logger} args.log - the logger used for logs in this session.
     * @param {(user:string, database:string) => void} args.homeDatabaseCallback - callback used to update the home database cache
     */
    function Session(_a) {
        var mode = _a.mode, connectionProvider = _a.connectionProvider, bookmarks = _a.bookmarks, database = _a.database, config = _a.config, reactive = _a.reactive, fetchSize = _a.fetchSize, impersonatedUser = _a.impersonatedUser, bookmarkManager = _a.bookmarkManager, notificationFilter = _a.notificationFilter, auth = _a.auth, log = _a.log, homeDatabaseCallback = _a.homeDatabaseCallback;
        var _b;
        this._mode = mode;
        this._database = database;
        this._reactive = reactive;
        this._fetchSize = fetchSize;
        this._homeDatabaseCallback = homeDatabaseCallback;
        this._auth = auth;
        this._getConnectionAcquistionBookmarks = this._getConnectionAcquistionBookmarks.bind(this);
        this._readConnectionHolder = new connection_holder_1.ConnectionHolder({
            mode: constants_1.ACCESS_MODE_READ,
            auth: auth,
            database: database,
            bookmarks: bookmarks,
            connectionProvider: connectionProvider,
            impersonatedUser: impersonatedUser,
            onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this),
            getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks,
            log: log
        });
        this._writeConnectionHolder = new connection_holder_1.ConnectionHolder({
            mode: constants_1.ACCESS_MODE_WRITE,
            auth: auth,
            database: database,
            bookmarks: bookmarks,
            connectionProvider: connectionProvider,
            impersonatedUser: impersonatedUser,
            onDatabaseNameResolved: this._onDatabaseNameResolved.bind(this),
            getConnectionAcquistionBookmarks: this._getConnectionAcquistionBookmarks,
            log: log
        });
        this._open = true;
        this._hasTx = false;
        this._impersonatedUser = impersonatedUser;
        this._lastBookmarks = bookmarks !== null && bookmarks !== void 0 ? bookmarks : bookmarks_1.Bookmarks.empty();
        this._configuredBookmarks = this._lastBookmarks;
        this._transactionExecutor = _createTransactionExecutor(config);
        this._databaseNameResolved = this._database !== '';
        var calculatedWatermaks = this._calculateWatermaks();
        this._lowRecordWatermark = calculatedWatermaks.low;
        this._highRecordWatermark = calculatedWatermaks.high;
        this._results = [];
        this._bookmarkManager = bookmarkManager;
        this._notificationFilter = notificationFilter;
        this._log = log;
        this._databaseGuess = config === null || config === void 0 ? void 0 : config.cachedHomeDatabase;
        this._isRoutingSession = (_b = config === null || config === void 0 ? void 0 : config.routingDriver) !== null && _b !== void 0 ? _b : false;
    }
    /**
     * Run Cypher query
     * Could be called with a query object i.e.: `{text: "MATCH ...", parameters: {param: 1}}`
     * or with the query and parameters as separate arguments.
     *
     * @public
     * @param {mixed} query - Cypher query to execute
     * @param {Object} parameters - Map with parameters to use in query
     * @param {TransactionConfig} [transactionConfig] - Configuration for the new auto-commit transaction.
     * @return {Result} New Result.
     */
    Session.prototype.run = function (query, parameters, transactionConfig) {
        var _this = this;
        var _a = (0, util_1.validateQueryAndParameters)(query, parameters), validatedQuery = _a.validatedQuery, params = _a.params;
        var autoCommitTxConfig = (transactionConfig != null)
            ? new tx_config_1.TxConfig(transactionConfig, this._log)
            : tx_config_1.TxConfig.empty();
        var result = this._run(validatedQuery, params, function (connection) { return __awaiter(_this, void 0, void 0, function () {
            var bookmarks;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this._bookmarks()];
                    case 1:
                        bookmarks = _a.sent();
                        this._assertSessionIsOpen();
                        return [2 /*return*/, connection.run(validatedQuery, params, {
                                bookmarks: bookmarks,
                                txConfig: autoCommitTxConfig,
                                mode: this._mode,
                                database: this._database,
                                apiTelemetryConfig: {
                                    api: constants_1.TELEMETRY_APIS.AUTO_COMMIT_TRANSACTION
                                },
                                impersonatedUser: this._impersonatedUser,
                                afterComplete: function (meta) { return _this._onCompleteCallback(meta, bookmarks); },
                                reactive: this._reactive,
                                fetchSize: this._fetchSize,
                                lowRecordWatermark: this._lowRecordWatermark,
                                highRecordWatermark: this._highRecordWatermark,
                                notificationFilter: this._notificationFilter,
                                onDb: this._onDatabaseNameResolved.bind(this)
                            })];
                }
            });
        }); });
        this._results.push(result);
        return result;
    };
    Session.prototype._run = function (query, parameters, customRunner) {
        var _a = this._acquireAndConsumeConnection(customRunner), connectionHolder = _a.connectionHolder, resultPromise = _a.resultPromise;
        var observerPromise = resultPromise.catch(function (error) { return Promise.resolve(new observers_1.FailedObserver({ error: error })); });
        var watermarks = { high: this._highRecordWatermark, low: this._lowRecordWatermark };
        return new result_1.default(observerPromise, query, parameters, connectionHolder, watermarks);
    };
    /**
     * This method is used by Rediscovery on the neo4j-driver-bolt-protocol package.
     *
     * @private
     * @param {function()} connectionConsumer The method which will use the connection
     * @returns {Promise<T>} A connection promise
     */
    Session.prototype._acquireConnection = function (connectionConsumer) {
        var _this = this;
        var _a = this._acquireAndConsumeConnection(connectionConsumer), connectionHolder = _a.connectionHolder, resultPromise = _a.resultPromise;
        return resultPromise.then(function (result) { return __awaiter(_this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, connectionHolder.releaseConnection()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/, result];
                }
            });
        }); });
    };
    /**
     * Acquires a {@link Connection}, consume it and return a promise of the result along with
     * the {@link ConnectionHolder} used in the process.
     *
     * @private
     * @param connectionConsumer
     * @returns {object} The connection holder and connection promise.
     */
    Session.prototype._acquireAndConsumeConnection = function (connectionConsumer) {
        var resultPromise;
        var connectionHolder = this._connectionHolderWithMode(this._mode);
        if (!this._open) {
            resultPromise = Promise.reject((0, error_1.newError)('Cannot run query in a closed session.'));
        }
        else if (!this._hasTx && connectionHolder.initializeConnection(this._databaseGuess)) {
            resultPromise = connectionHolder
                .getConnection()
                // Connection won't be null at this point since the initialize method
                // return
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                .then(function (connection) { return connectionConsumer(connection); });
        }
        else {
            resultPromise = Promise.reject((0, error_1.newError)('Queries cannot be run directly on a ' +
                'session with an open transaction; either run from within the ' +
                'transaction or use a different session.'));
        }
        return { connectionHolder: connectionHolder, resultPromise: resultPromise };
    };
    /**
     * Begin a new transaction in this session. A session can have at most one transaction running at a time, if you
     * want to run multiple concurrent transactions, you should use multiple concurrent sessions.
     *
     * While a transaction is open the session cannot be used to run queries outside the transaction.
     *
     * @param {TransactionConfig} [transactionConfig] - Configuration for the new auto-commit transaction.
     * @returns {TransactionPromise} New Transaction.
     */
    Session.prototype.beginTransaction = function (transactionConfig) {
        // this function needs to support bookmarks parameter for backwards compatibility
        // parameter was of type {string|string[]} and represented either a single or multiple bookmarks
        // that's why we need to check parameter type and decide how to interpret the value
        var arg = transactionConfig;
        var txConfig = tx_config_1.TxConfig.empty();
        if (arg != null) {
            txConfig = new tx_config_1.TxConfig(arg, this._log);
        }
        return this._beginTransaction(this._mode, txConfig, { api: constants_1.TELEMETRY_APIS.UNMANAGED_TRANSACTION });
    };
    Session.prototype._beginTransaction = function (accessMode, txConfig, apiTelemetryConfig) {
        var _this = this;
        if (!this._open) {
            throw (0, error_1.newError)('Cannot begin a transaction on a closed session.');
        }
        if (this._hasTx) {
            throw (0, error_1.newError)('You cannot begin a transaction on a session with an open transaction; ' +
                'either run from within the transaction or use a different session.');
        }
        var mode = Session._validateSessionMode(accessMode);
        var connectionHolder = this._connectionHolderWithMode(mode);
        connectionHolder.initializeConnection(this._databaseGuess);
        this._hasTx = true;
        var tx = new transaction_promise_1.default({
            connectionHolder: connectionHolder,
            impersonatedUser: this._impersonatedUser,
            onClose: this._transactionClosed.bind(this),
            onBookmarks: function (newBm, oldBm, db) { return _this._updateBookmarks(newBm, oldBm, db); },
            onConnection: this._assertSessionIsOpen.bind(this),
            reactive: this._reactive,
            fetchSize: this._fetchSize,
            lowRecordWatermark: this._lowRecordWatermark,
            highRecordWatermark: this._highRecordWatermark,
            notificationFilter: this._notificationFilter,
            apiTelemetryConfig: apiTelemetryConfig,
            onDbCallback: this._onDatabaseNameResolved.bind(this)
        });
        tx._begin(function () { return _this._bookmarks(); }, txConfig);
        return tx;
    };
    /**
     * @private
     * @returns {void}
     */
    Session.prototype._assertSessionIsOpen = function () {
        if (!this._open) {
            throw (0, error_1.newError)('You cannot run more transactions on a closed session.');
        }
    };
    /**
     * @private
     * @returns {void}
     */
    Session.prototype._transactionClosed = function () {
        this._hasTx = false;
    };
    /**
     * Return the bookmarks received following the last completed {@link Transaction}.
     *
     * @deprecated This method will be removed in version 6.0. Please, use {@link Session#lastBookmarks} instead.
     *
     * @return {string[]} A reference to a previous transaction.
     * @see {@link Session#lastBookmarks}
     */
    Session.prototype.lastBookmark = function () {
        return this.lastBookmarks();
    };
    /**
     * Return the bookmarks received following the last completed {@link Transaction}.
     *
     * @return {string[]} A reference to a previous transaction.
     */
    Session.prototype.lastBookmarks = function () {
        return this._lastBookmarks.values();
    };
    Session.prototype._bookmarks = function () {
        return __awaiter(this, void 0, void 0, function () {
            var bookmarks;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, ((_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.getBookmarks())];
                    case 1:
                        bookmarks = _b.sent();
                        if (bookmarks === undefined) {
                            return [2 /*return*/, this._lastBookmarks];
                        }
                        return [2 /*return*/, new bookmarks_1.Bookmarks(__spreadArray(__spreadArray([], __read(bookmarks), false), __read(this._configuredBookmarks), false))];
                }
            });
        });
    };
    /**
     * Execute given unit of work in a {@link READ} transaction.
     *
     * Transaction will automatically be committed unless the given function throws or returns a rejected promise.
     * Some failures of the given function or the commit itself will be retried with exponential backoff with initial
     * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
     * `maxTransactionRetryTime` property in milliseconds.
     *
     * @deprecated This method will be removed in version 6.0. Please, use {@link Session#executeRead} instead.
     *
     * @param {function(tx: Transaction): Promise} transactionWork - Callback that executes operations against
     * a given {@link Transaction}.
     * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work.
     * @return {Promise} Resolved promise as returned by the given function or rejected promise when given
     * function or commit fails.
     * @see {@link Session#executeRead}
     */
    Session.prototype.readTransaction = function (transactionWork, transactionConfig) {
        var config = new tx_config_1.TxConfig(transactionConfig, this._log);
        return this._runTransaction(constants_1.ACCESS_MODE_READ, config, transactionWork);
    };
    /**
     * Execute given unit of work in a {@link WRITE} transaction.
     *
     * Transaction will automatically be committed unless the given function throws or returns a rejected promise.
     * Some failures of the given function or the commit itself will be retried with exponential backoff with initial
     * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
     * `maxTransactionRetryTime` property in milliseconds.
     *
     * @deprecated This method will be removed in version 6.0. Please, use {@link Session#executeWrite} instead.
     *
     * @param {function(tx: Transaction): Promise} transactionWork - Callback that executes operations against
     * a given {@link Transaction}.
     * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work.
     * @return {Promise} Resolved promise as returned by the given function or rejected promise when given
     * function or commit fails.
     * @see {@link Session#executeWrite}
     */
    Session.prototype.writeTransaction = function (transactionWork, transactionConfig) {
        var config = new tx_config_1.TxConfig(transactionConfig, this._log);
        return this._runTransaction(constants_1.ACCESS_MODE_WRITE, config, transactionWork);
    };
    Session.prototype._runTransaction = function (accessMode, transactionConfig, transactionWork) {
        var _this = this;
        return this._transactionExecutor.execute(function (apiTelemetryConfig) { return _this._beginTransaction(accessMode, transactionConfig, apiTelemetryConfig); }, transactionWork);
    };
    /**
     * Execute given unit of work in a {@link READ} transaction.
     *
     * Transaction will automatically be committed unless the given function throws or returns a rejected promise.
     * Some failures of the given function or the commit itself will be retried with exponential backoff with initial
     * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
     * `maxTransactionRetryTime` property in milliseconds.
     *
     * 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).
     *
     * @param {function(tx: ManagedTransaction): Promise} transactionWork - Callback that executes operations against
     * a given {@link Transaction}.
     * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work.
     * @return {Promise} Resolved promise as returned by the given function or rejected promise when given
     * function or commit fails.
     */
    Session.prototype.executeRead = function (transactionWork, transactionConfig) {
        var config = new tx_config_1.TxConfig(transactionConfig, this._log);
        return this._executeInTransaction(constants_1.ACCESS_MODE_READ, config, transactionWork);
    };
    /**
     * Execute given unit of work in a {@link WRITE} transaction.
     *
     * Transaction will automatically be committed unless the given function throws or returns a rejected promise.
     * Some failures of the given function or the commit itself will be retried with exponential backoff with initial
     * delay of 1 second and maximum retry time of 30 seconds. Maximum retry time is configurable via driver config's
     * `maxTransactionRetryTime` property in milliseconds.
     *
     * 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).
     *
     * @param {function(tx: ManagedTransaction): Promise} transactionWork - Callback that executes operations against
     * a given {@link Transaction}.
     * @param {TransactionConfig} [transactionConfig] - Configuration for all transactions started to execute the unit of work.
     * @return {Promise} Resolved promise as returned by the given function or rejected promise when given
     * function or commit fails.
     */
    Session.prototype.executeWrite = function (transactionWork, transactionConfig) {
        var config = new tx_config_1.TxConfig(transactionConfig, this._log);
        return this._executeInTransaction(constants_1.ACCESS_MODE_WRITE, config, transactionWork);
    };
    /**
     * @private
     * @param {SessionMode} accessMode
     * @param {TxConfig} transactionConfig
     * @param {ManagedTransactionWork} transactionWork
     * @returns {Promise}
     */
    Session.prototype._executeInTransaction = function (accessMode, transactionConfig, transactionWork) {
        var _this = this;
        return this._transactionExecutor.execute(function (apiTelemetryConfig) { return _this._beginTransaction(accessMode, transactionConfig, apiTelemetryConfig); }, transactionWork, transaction_managed_1.default.fromTransaction);
    };
    /**
     * Sets the resolved database name in the session context.
     * @private
     * @param {string|undefined} database The resolved database name
     * @returns {void}
     */
    Session.prototype._onDatabaseNameResolved = function (database) {
        if (this._isRoutingSession) {
            this._databaseGuess = database;
            if (!this._databaseNameResolved) {
                var normalizedDatabase = database !== null && database !== void 0 ? database : '';
                this._database = normalizedDatabase;
                this._readConnectionHolder.setDatabase(normalizedDatabase);
                this._writeConnectionHolder.setDatabase(normalizedDatabase);
                this._databaseNameResolved = true;
                if (this._homeDatabaseCallback != null) {
                    this._homeDatabaseCallback((0, auth_util_1.cacheKey)(this._auth, this._impersonatedUser), database);
                }
            }
        }
    };
    Session.prototype._getConnectionAcquistionBookmarks = function () {
        return __awaiter(this, void 0, void 0, function () {
            var bookmarks;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, ((_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.getBookmarks())];
                    case 1:
                        bookmarks = _b.sent();
                        if (bookmarks === undefined) {
                            return [2 /*return*/, this._lastBookmarks];
                        }
                        return [2 /*return*/, new bookmarks_1.Bookmarks(__spreadArray(__spreadArray([], __read(this._configuredBookmarks), false), __read(bookmarks), false))];
                }
            });
        });
    };
    /**
     * Update value of the last bookmarks.
     * @private
     * @param {Bookmarks} newBookmarks - The new bookmarks.
     * @returns {void}
     */
    Session.prototype._updateBookmarks = function (newBookmarks, previousBookmarks, database) {
        var _a, _b, _c;
        if ((newBookmarks != null) && !newBookmarks.isEmpty()) {
            (_a = this._bookmarkManager) === null || _a === void 0 ? void 0 : _a.updateBookmarks((_b = previousBookmarks === null || previousBookmarks === void 0 ? void 0 : previousBookmarks.values()) !== null && _b !== void 0 ? _b : [], (_c = newBookmarks === null || newBookmarks === void 0 ? void 0 : newBookmarks.values()) !== null && _c !== void 0 ? _c : []).catch(function () { });
            this._lastBookmarks = newBookmarks;
            this._configuredBookmarks = bookmarks_1.Bookmarks.empty();
        }
    };
    /**
     * Close this session.
     * @return {Promise}
     */
    Session.prototype.close = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this._open) return [3 /*break*/, 3];
                        this._open = false;
                        this._results.forEach(function (result) { return result._cancel(); });
                        this._transactionExecutor.close();
                        return [4 /*yield*/, this._readConnectionHolder.close(this._hasTx)];
                    case 1:
                        _a.sent();
                        return [4 /*yield*/, this._writeConnectionHolder.close(this._hasTx)];
                    case 2:
                        _a.sent();
                        _a.label = 3;
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    // eslint-disable-next-line
    // @ts-ignore
    Session.prototype[Symbol.asyncDispose] = function () {
        return this.close();
    };
    Session.prototype._connectionHolderWithMode = function (mode) {
        if (mode === constants_1.ACCESS_MODE_READ) {
            return this._readConnectionHolder;
        }
        else if (mode === constants_1.ACCESS_MODE_WRITE) {
            return this._writeConnectionHolder;
        }
        else {
            throw (0, error_1.newError)('Unknown access mode: ' + mode);
        }
    };
    /**
     * @private
     * @param {Object} meta Connection metadatada
     * @returns {void}
     */
    Session.prototype._onCompleteCallback = function (meta, previousBookmarks) {
        this._updateBookmarks(new bookmarks_1.Bookmarks(meta.bookmark), previousBookmarks, meta.db);
    };
    /**
     * @private
     * @returns {void}
     */
    Session.prototype._calculateWatermaks = function () {
        if (this._fetchSize === constants_1.FETCH_ALL) {
            return {
                low: Number.MAX_VALUE, // we shall always lower than this number to enable auto pull
                high: Number.MAX_VALUE // we shall never reach this number to disable auto pull
            };
        }
        return {
            low: 0.3 * this._fetchSize,
            high: 0.7 * this._fetchSize
        };
    };
    /**
     * Configure the transaction executor
     *
     * This used by {@link Driver#executeQuery}
     * @private
     * @returns {void}
     */
    Session.prototype._configureTransactionExecutor = function (pipelined, telemetryApi) {
        this._transactionExecutor.pipelineBegin = pipelined;
        this._transactionExecutor.telemetryApi = telemetryApi;
    };
    /**
     * @protected
     */
    Session._validateSessionMode = function (rawMode) {
        var mode = rawMode !== null && rawMode !== void 0 ? rawMode : constants_1.ACCESS_MODE_WRITE;
        if (mode !== constants_1.ACCESS_MODE_READ && mode !== constants_1.ACCESS_MODE_WRITE) {
            throw (0, error_1.newError)('Illegal session mode ' + mode);
        }
        return mode;
    };
    return Session;
}());
/**
 * @private
 * @param {object} config
 * @returns {TransactionExecutor} The transaction executor
 */
function _createTransactionExecutor(config) {
    var _a;
    var maxRetryTimeMs = (_a = config === null || config === void 0 ? void 0 : config.maxTransactionRetryTime) !== null && _a !== void 0 ? _a : null;
    return new transaction_executor_1.TransactionExecutor(maxRetryTimeMs);
}
exports.default = Session;
