"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(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;
    return g = { next: verb(0), "throw": verb(1), "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 (_) try {
            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [0, 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 };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
var RepositoryNotFoundError_1 = require("./error/RepositoryNotFoundError");
var DefaultNamingStrategy_1 = require("../naming-strategy/DefaultNamingStrategy");
var CannotExecuteNotConnectedError_1 = require("./error/CannotExecuteNotConnectedError");
var CannotConnectAlreadyConnectedError_1 = require("./error/CannotConnectAlreadyConnectedError");
var TreeRepository_1 = require("../repository/TreeRepository");
var RepositoryNotTreeError_1 = require("./error/RepositoryNotTreeError");
var EntityMetadataNotFound_1 = require("../metadata-args/error/EntityMetadataNotFound");
var MigrationExecutor_1 = require("../migration/MigrationExecutor");
var PlatformTools_1 = require("../platform/PlatformTools");
var MongoDriver_1 = require("../driver/mongodb/MongoDriver");
var MongoEntityManager_1 = require("../entity-manager/MongoEntityManager");
var EntityMetadataValidator_1 = require("../metadata-builder/EntityMetadataValidator");
var QueryRunnerProviderAlreadyReleasedError_1 = require("../query-runner/error/QueryRunnerProviderAlreadyReleasedError");
var QueryBuilder_1 = require("../query-builder/QueryBuilder");
var EntityManagerFactory_1 = require("../entity-manager/EntityManagerFactory");
var LoggerFactory_1 = require("../logger/LoggerFactory");
var RepositoryFactory_1 = require("../repository/RepositoryFactory");
var DriverFactory_1 = require("../driver/DriverFactory");
var ConnectionMetadataBuilder_1 = require("./ConnectionMetadataBuilder");
/**
 * Connection is a single database ORM connection to a specific DBMS database.
 * Its not required to be a database connection, depend on database type it can create connection pool.
 * You can have multiple connections to multiple databases in your application.
 */
var Connection = (function () {
    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------
    function Connection(options) {
        /**
         * Indicates if connection is initialized or not.
         */
        this.isConnected = false;
        /**
         * Migration instances that are registered for this connection.
         */
        this.migrations = [];
        /**
         * Entity subscriber instances that are registered for this connection.
         */
        this.subscribers = [];
        /**
         * All entity metadatas that are registered for this connection.
         */
        this.entityMetadatas = [];
        this.name = options.name || "default";
        this.options = options;
        this.logger = new LoggerFactory_1.LoggerFactory().create(this.options.logging || {});
        this.driver = new DriverFactory_1.DriverFactory().create(this);
        this.manager = new EntityManagerFactory_1.EntityManagerFactory().create(this);
        this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy_1.DefaultNamingStrategy();
    }
    Object.defineProperty(Connection.prototype, "mongoEntityManager", {
        // -------------------------------------------------------------------------
        // Public Accessors
        // -------------------------------------------------------------------------
        /**
         * Gets the mongodb entity manager that allows to perform mongodb-specific repository operations
         * with any entity in this connection.
         *
         * Available only in mongodb connections.
         */
        get: function () {
            if (!(this.manager instanceof MongoEntityManager_1.MongoEntityManager))
                throw new Error("MongoEntityManager is only available for MongoDB databases.");
            return this.manager;
        },
        enumerable: true,
        configurable: true
    });
    // -------------------------------------------------------------------------
    // Public Methods
    // -------------------------------------------------------------------------
    /**
     * Performs connection to the database.
     * This method should be called once on application bootstrap.
     * This method not necessarily creates database connection (depend on database type),
     * but it also can setup a connection pool with database to use.
     */
    Connection.prototype.connect = function () {
        return __awaiter(this, void 0, void 0, function () {
            var error_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this.isConnected)
                            throw new CannotConnectAlreadyConnectedError_1.CannotConnectAlreadyConnectedError(this.name);
                        // connect to the database via its driver
                        return [4 /*yield*/, this.driver.connect()];
                    case 1:
                        // connect to the database via its driver
                        _a.sent();
                        // set connected status for the current connection
                        Object.assign(this, { isConnected: true });
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 9, , 11]);
                        // build all metadatas registered in the current connection
                        this.buildMetadatas();
                        if (!(this.options.dropSchemaOnConnection && !PlatformTools_1.PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))) return [3 /*break*/, 4];
                        return [4 /*yield*/, this.dropDatabase()];
                    case 3:
                        _a.sent();
                        _a.label = 4;
                    case 4:
                        if (!(this.options.autoSchemaSync && !PlatformTools_1.PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))) return [3 /*break*/, 6];
                        return [4 /*yield*/, this.syncSchema()];
                    case 5:
                        _a.sent();
                        _a.label = 6;
                    case 6:
                        if (!(this.options.autoMigrationsRun && !PlatformTools_1.PlatformTools.getEnvVariable("SKIP_MIGRATIONS_RUN"))) return [3 /*break*/, 8];
                        return [4 /*yield*/, this.runMigrations()];
                    case 7:
                        _a.sent();
                        _a.label = 8;
                    case 8: return [3 /*break*/, 11];
                    case 9:
                        error_1 = _a.sent();
                        // if for some reason build metadata fail (for example validation error during entity metadata check)
                        // connection needs to be closed
                        return [4 /*yield*/, this.close()];
                    case 10:
                        // if for some reason build metadata fail (for example validation error during entity metadata check)
                        // connection needs to be closed
                        _a.sent();
                        throw error_1;
                    case 11: return [2 /*return*/, this];
                }
            });
        });
    };
    /**
     * Closes connection with the database.
     * Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
     */
    Connection.prototype.close = function () {
        return __awaiter(this, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.isConnected)
                            throw new CannotExecuteNotConnectedError_1.CannotExecuteNotConnectedError(this.name);
                        return [4 /*yield*/, this.driver.disconnect()];
                    case 1:
                        _a.sent();
                        Object.assign(this, { isConnected: false });
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Creates database schema for all entities registered in this connection.
     * Can be used only after connection to the database is established.
     *
     * @param dropBeforeSync If set to true then it drops the database with all its tables and data
     */
    Connection.prototype.syncSchema = function (dropBeforeSync) {
        if (dropBeforeSync === void 0) { dropBeforeSync = false; }
        return __awaiter(this, void 0, void 0, function () {
            var schemaBuilder;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.isConnected)
                            throw new CannotExecuteNotConnectedError_1.CannotExecuteNotConnectedError(this.name);
                        if (!dropBeforeSync) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.dropDatabase()];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2:
                        schemaBuilder = this.driver.createSchemaBuilder();
                        return [4 /*yield*/, schemaBuilder.build()];
                    case 3:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Drops the database and all its data.
     * Be careful with this method on production since this method will erase all your database tables and their data.
     * Can be used only after connection to the database is established.
     */
    Connection.prototype.dropDatabase = function () {
        return __awaiter(this, void 0, void 0, function () {
            var queryRunner;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.driver.createQueryRunner()];
                    case 1:
                        queryRunner = _a.sent();
                        return [4 /*yield*/, queryRunner.clearDatabase()];
                    case 2:
                        _a.sent();
                        return [4 /*yield*/, queryRunner.release()];
                    case 3:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Runs all pending migrations.
     * Can be used only after connection to the database is established.
     */
    Connection.prototype.runMigrations = function () {
        return __awaiter(this, void 0, void 0, function () {
            var migrationExecutor;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.isConnected)
                            throw new CannotExecuteNotConnectedError_1.CannotExecuteNotConnectedError(this.name);
                        migrationExecutor = new MigrationExecutor_1.MigrationExecutor(this);
                        return [4 /*yield*/, migrationExecutor.executePendingMigrations()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Reverts last executed migration.
     * Can be used only after connection to the database is established.
     */
    Connection.prototype.undoLastMigration = function () {
        return __awaiter(this, void 0, void 0, function () {
            var migrationExecutor;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.isConnected)
                            throw new CannotExecuteNotConnectedError_1.CannotExecuteNotConnectedError(this.name);
                        migrationExecutor = new MigrationExecutor_1.MigrationExecutor(this);
                        return [4 /*yield*/, migrationExecutor.undoLastMigration()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Checks if entity metadata exist for the given entity class, target name or table name.
     */
    Connection.prototype.hasMetadata = function (target) {
        return !!this.findMetadata(target);
    };
    /**
     * Gets entity metadata for the given entity class or schema name.
     */
    Connection.prototype.getMetadata = function (target) {
        var metadata = this.findMetadata(target);
        if (!metadata)
            throw new EntityMetadataNotFound_1.EntityMetadataNotFound(target);
        return metadata;
    };
    /**
     * Gets repository for the given entity.
     */
    Connection.prototype.getRepository = function (target) {
        return this.getMetadata(target).repository;
    };
    /**
     * Gets tree repository for the given entity class or name.
     * Only tree-type entities can have a TreeRepository, like ones decorated with @ClosureEntity decorator.
     */
    Connection.prototype.getTreeRepository = function (target) {
        if (this.driver instanceof MongoDriver_1.MongoDriver)
            throw new Error("You cannot use getTreeRepository for MongoDB connections.");
        if (!this.hasMetadata(target))
            throw new RepositoryNotFoundError_1.RepositoryNotFoundError(this.name, target);
        var repository = this.getMetadata(target).repository;
        if (!(repository instanceof TreeRepository_1.TreeRepository))
            throw new RepositoryNotTreeError_1.RepositoryNotTreeError(target);
        return repository;
    };
    /**
     * Gets mongodb-specific repository for the given entity class or name.
     * Works only if connection is mongodb-specific.
     */
    Connection.prototype.getMongoRepository = function (target) {
        if (!(this.driver instanceof MongoDriver_1.MongoDriver))
            throw new Error("You can use getMongoRepository only for MongoDB connections.");
        if (!this.hasMetadata(target))
            throw new RepositoryNotFoundError_1.RepositoryNotFoundError(this.name, target);
        return this.getMetadata(target).repository;
    };
    /**
     * Gets custom entity repository marked with @EntityRepository decorator.
     */
    Connection.prototype.getCustomRepository = function (customRepository) {
        return this.manager.getCustomRepository(customRepository);
    };
    /**
     * Wraps given function execution (and all operations made there) into a transaction.
     * All database operations must be executed using provided entity manager.
     */
    Connection.prototype.transaction = function (runInTransaction, queryRunner) {
        return __awaiter(this, void 0, void 0, function () {
            var usedQueryRunner, transactionEntityManager, result, err_1, rollbackError_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this instanceof MongoEntityManager_1.MongoEntityManager)
                            throw new Error("Transactions aren't supported by MongoDB.");
                        if (queryRunner && queryRunner.isReleased)
                            throw new QueryRunnerProviderAlreadyReleasedError_1.QueryRunnerProviderAlreadyReleasedError();
                        usedQueryRunner = queryRunner || this.driver.createQueryRunner();
                        transactionEntityManager = new EntityManagerFactory_1.EntityManagerFactory().create(this, usedQueryRunner);
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 5, 10, 13]);
                        return [4 /*yield*/, usedQueryRunner.startTransaction()];
                    case 2:
                        _a.sent();
                        return [4 /*yield*/, runInTransaction(transactionEntityManager)];
                    case 3:
                        result = _a.sent();
                        return [4 /*yield*/, usedQueryRunner.commitTransaction()];
                    case 4:
                        _a.sent();
                        return [2 /*return*/, result];
                    case 5:
                        err_1 = _a.sent();
                        _a.label = 6;
                    case 6:
                        _a.trys.push([6, 8, , 9]);
                        return [4 /*yield*/, usedQueryRunner.rollbackTransaction()];
                    case 7:
                        _a.sent();
                        return [3 /*break*/, 9];
                    case 8:
                        rollbackError_1 = _a.sent();
                        return [3 /*break*/, 9];
                    case 9: throw err_1;
                    case 10:
                        if (!!queryRunner) return [3 /*break*/, 12];
                        return [4 /*yield*/, usedQueryRunner.release()];
                    case 11:
                        _a.sent();
                        _a.label = 12;
                    case 12: return [7 /*endfinally*/];
                    case 13: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Executes raw SQL query and returns raw database results.
     */
    Connection.prototype.query = function (query, parameters, queryRunner) {
        return __awaiter(this, void 0, void 0, function () {
            var usedQueryRunner;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this instanceof MongoEntityManager_1.MongoEntityManager)
                            throw new Error("Queries aren't supported by MongoDB.");
                        if (queryRunner && queryRunner.isReleased)
                            throw new QueryRunnerProviderAlreadyReleasedError_1.QueryRunnerProviderAlreadyReleasedError();
                        usedQueryRunner = queryRunner || this.driver.createQueryRunner();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, , 3, 6]);
                        return [4 /*yield*/, usedQueryRunner.query(query, parameters)];
                    case 2: return [2 /*return*/, _a.sent()]; // await is needed here because we are using finally
                    case 3:
                        if (!!queryRunner) return [3 /*break*/, 5];
                        return [4 /*yield*/, usedQueryRunner.release()];
                    case 4:
                        _a.sent();
                        _a.label = 5;
                    case 5: return [7 /*endfinally*/];
                    case 6: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Creates a new query builder that can be used to build a sql query.
     */
    Connection.prototype.createQueryBuilder = function (entityClass, alias, queryRunner) {
        if (this instanceof MongoEntityManager_1.MongoEntityManager)
            throw new Error("Query Builder is not supported by MongoDB.");
        if (alias) {
            var metadata = this.getMetadata(entityClass);
            return new QueryBuilder_1.QueryBuilder(this, queryRunner)
                .select(alias)
                .from(metadata.target, alias);
        }
        else {
            return new QueryBuilder_1.QueryBuilder(this, entityClass);
        }
    };
    /**
     * Creates a new entity manager with a single opened connection to the database.
     * This may be useful if you want to perform all db queries within one connection.
     * After finishing with entity manager, don't forget to release it (to release database connection back to pool).
     */
    Connection.prototype.createIsolatedManager = function (queryRunner) {
        if (!queryRunner)
            queryRunner = this.driver.createQueryRunner();
        return new EntityManagerFactory_1.EntityManagerFactory().create(this, queryRunner);
    };
    /**
     * Creates a new repository with a single opened connection to the database.
     * This may be useful if you want to perform all db queries within one connection.
     * After finishing with entity manager, don't forget to release it (to release database connection back to pool).
     */
    Connection.prototype.createIsolatedRepository = function (entityClassOrName, queryRunner) {
        if (!queryRunner)
            queryRunner = this.driver.createQueryRunner();
        return new RepositoryFactory_1.RepositoryFactory().createRepository(this, this.getMetadata(entityClassOrName), queryRunner);
    };
    /**
     * Creates a new specific repository with a single opened connection to the database.
     * This may be useful if you want to perform all db queries within one connection.
     * After finishing with entity manager, don't forget to release it (to release database connection back to pool).
     */
    Connection.prototype.createIsolatedSpecificRepository = function (entityClassOrName, queryRunner) {
        if (!queryRunner)
            queryRunner = this.driver.createQueryRunner();
        return new RepositoryFactory_1.RepositoryFactory().createSpecificRepository(this, this.getMetadata(entityClassOrName), queryRunner);
    };
    Object.defineProperty(Connection.prototype, "entityManager", {
        // -------------------------------------------------------------------------
        // Deprecated Public Methods
        // -------------------------------------------------------------------------
        /**
         * Gets entity manager that allows to perform repository operations with any entity in this connection.
         *
         * @deprecated use manager instead.
         */
        get: function () {
            return this.manager;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Gets specific repository for the given entity class or name.
     * SpecificRepository is a special repository that contains specific and non standard repository methods.
     *
     * @deprecated don't use it, it will be refactored or removed in the future versions
     */
    Connection.prototype.getSpecificRepository = function (entityClassOrName) {
        if (!this.hasMetadata(entityClassOrName))
            throw new RepositoryNotFoundError_1.RepositoryNotFoundError(this.name, entityClassOrName);
        return this.getMetadata(entityClassOrName).specificRepository;
    };
    // -------------------------------------------------------------------------
    // Protected Methods
    // -------------------------------------------------------------------------
    /**
     * Finds exist entity metadata by the given entity class, target name or table name.
     */
    Connection.prototype.findMetadata = function (target) {
        return this.entityMetadatas.find(function (metadata) {
            if (metadata.target === target)
                return true;
            if (typeof target === "string")
                return metadata.name === target || metadata.tableName === target;
            return false;
        });
    };
    /**
     * Builds metadatas for all registered classes inside this connection.
     */
    Connection.prototype.buildMetadatas = function () {
        var _this = this;
        var connectionMetadataBuilder = new ConnectionMetadataBuilder_1.ConnectionMetadataBuilder(this);
        var repositoryFactory = new RepositoryFactory_1.RepositoryFactory();
        var entityMetadataValidator = new EntityMetadataValidator_1.EntityMetadataValidator();
        // create subscribers instances if they are not disallowed from high-level (for example they can disallowed from migrations run process)
        if (!PlatformTools_1.PlatformTools.getEnvVariable("SKIP_SUBSCRIBERS_LOADING")) {
            var subscribers = connectionMetadataBuilder.buildSubscribers(this.options.subscribers || []);
            Object.assign(this, { subscribers: subscribers });
        }
        // build entity metadatas
        var entityMetadatas = connectionMetadataBuilder.buildEntityMetadatas(this.options.entities || [], this.options.entitySchemas || []);
        Object.assign(this, { entityMetadatas: entityMetadatas });
        // create migration instances
        var migrations = connectionMetadataBuilder.buildMigrations(this.options.migrations || []);
        Object.assign(this, { migrations: migrations });
        // initialize repositories for all entity metadatas
        this.entityMetadatas.forEach(function (metadata) {
            metadata.repository = repositoryFactory.createRepository(_this, metadata);
            metadata.specificRepository = repositoryFactory.createSpecificRepository(_this, metadata);
        });
        // validate all created entity metadatas to make sure user created entities are valid and correct
        entityMetadataValidator.validateMany(this.entityMetadatas);
    };
    return Connection;
}());
exports.Connection = Connection;

//# sourceMappingURL=Connection.js.map
