#62 and #61 - removed all third party dependencies that could be removed for browser platform; added platform tools classes and extracted all platform-specific code there; removed mysql2 driver type - mysql2 can be used if mysql is not installed by mysql2 is

This commit is contained in:
Umed Khudoiberdiev 2016-12-03 12:22:09 +05:00
parent 9386825b17
commit 4a85607da1
20 changed files with 592 additions and 127 deletions

View File

@ -58,17 +58,10 @@
"typescript": "^2.1.1"
},
"dependencies": {
"@types/lodash": "4.14.40",
"@types/node": "^6.0.50",
"app-root-path": "^2.0.1",
"dependency-graph": "^0.5.0",
"glob": "^7.1.1",
"lodash": "^4.17.2",
"path": "^0.12.7",
"reflect-metadata": "^0.1.8",
"require-all": "^2.0.0",
"sha1": "^1.1.1",
"url": "^0.11.0",
"yargonaut": "^1.1.2",
"yargs": "^6.4.0"
},

View File

@ -1,17 +1,17 @@
import * as _ from "lodash";
import {NamingStrategyInterface} from "../../../src/naming-strategy/NamingStrategyInterface";
import {NamingStrategy} from "../../../src/decorator/NamingStrategy";
import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy";
import {snakeCase} from "../../../src/util/StringUtils";
@NamingStrategy("custom_strategy")
export class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string, customName: string): string {
return customName ? customName : _.snakeCase(className);
return customName ? customName : snakeCase(className);
}
columnName(propertyName: string, customName: string): string {
return customName ? customName : _.snakeCase(propertyName);
return customName ? customName : snakeCase(propertyName);
}
columnNameCustomized(customName: string): string {
@ -19,7 +19,7 @@ export class CustomNamingStrategy extends DefaultNamingStrategy implements Namin
}
relationName(propertyName: string): string {
return _.snakeCase(propertyName);
return snakeCase(propertyName);
}
}

View File

@ -1,4 +1,3 @@
import * as fs from "fs";
import {Connection} from "./Connection";
import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
@ -14,6 +13,7 @@ import {OracleDriver} from "../driver/oracle/OracleDriver";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {OrmUtils} from "../util/OrmUtils";
import {CannotDetermineConnectionOptionsError} from "./error/CannotDetermineConnectionOptionsError";
import {PlatformTools} from "../platform/PlatformTools";
/**
* ConnectionManager is used to store and manage all these different connections.
@ -118,7 +118,7 @@ export class ConnectionManager {
* it will try to create connection from environment variables.
* There are several environment variables you can set:
*
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_URL - database connection url. Should be a string.
* - TYPEORM_HOST - database host. Should be a string.
* - TYPEORM_PORT - database access port. Should be a number.
@ -186,7 +186,7 @@ export class ConnectionManager {
* it will try to create connection from environment variables.
* There are several environment variables you can set:
*
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_URL - database connection url. Should be a string.
* - TYPEORM_HOST - database host. Should be a string.
* - TYPEORM_PORT - database access port. Should be a number.
@ -254,18 +254,18 @@ export class ConnectionManager {
* Checks if ormconfig.json exists.
*/
protected hasOrmConfigurationFile(): boolean {
const path = require("app-root-path").path + "/ormconfig.json";
if (!fs.existsSync(path))
const path = PlatformTools.load("app-root-path").path + "/ormconfig.json";
if (!PlatformTools.fileExist(path))
return false;
const configuration: ConnectionOptions[]|ConnectionOptions = require(path);
const configuration: ConnectionOptions[]|ConnectionOptions = PlatformTools.load(path);
if (configuration instanceof Array) {
return configuration
.filter(options => !options.environment || options.environment === process.env.NODE_ENV)
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV"))
.length > 0;
} else if (configuration instanceof Object) {
if (configuration.environment && configuration.environment !== process.env.NODE_ENV)
if (configuration.environment && configuration.environment !== PlatformTools.getEnvVariable("NODE_ENV"))
return false;
return Object.keys(configuration).length > 0;
@ -278,14 +278,14 @@ export class ConnectionManager {
* Checks if there is a default connection in the ormconfig.json file.
*/
protected hasDefaultConfigurationInConfigurationFile(): boolean {
const path = require("app-root-path").path + "/ormconfig.json";
if (!fs.existsSync(path))
const path = PlatformTools.load("app-root-path").path + "/ormconfig.json";
if (!PlatformTools.fileExist(path))
return false;
const configuration: ConnectionOptions[]|ConnectionOptions = require(path);
const configuration: ConnectionOptions[]|ConnectionOptions = PlatformTools.load(path);
if (configuration instanceof Array) {
return !!configuration
.filter(options => !options.environment || options.environment === process.env.NODE_ENV)
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV"))
.find(config => !!config.name || config.name === "default");
} else if (configuration instanceof Object) {
@ -293,7 +293,7 @@ export class ConnectionManager {
configuration.name !== "default")
return false;
if (configuration.environment && configuration.environment !== process.env.NODE_ENV)
if (configuration.environment && configuration.environment !== PlatformTools.getEnvVariable("NODE_ENV"))
return false;
return true;
@ -306,7 +306,7 @@ export class ConnectionManager {
* Checks if environment variables contains connection options.
*/
protected hasDefaultConfigurationInEnvironmentVariables(): boolean {
return !!process.env.TYPEORM_DRIVER_TYPE;
return !!PlatformTools.getEnvVariable("TYPEORM_DRIVER_TYPE");
}
/**
@ -315,28 +315,28 @@ export class ConnectionManager {
protected async createFromEnvAndConnect(): Promise<Connection> {
return this.createAndConnectByConnectionOptions({
driver: {
type: process.env.TYPEORM_DRIVER_TYPE,
url: process.env.TYPEORM_URL,
host: process.env.TYPEORM_HOST,
port: process.env.TYPEORM_PORT,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
sid: process.env.TYPEORM_SID,
storage: process.env.TYPEORM_STORAGE,
usePool: process.env.TYPEORM_USE_POOL !== undefined ? OrmUtils.toBoolean(process.env.TYPEORM_USE_POOL) : undefined, // special check for defined is required here
extra: process.env.TYPEORM_DRIVER_EXTRA ? JSON.parse(process.env.TYPEORM_DRIVER_EXTRA) : undefined
type: PlatformTools.getEnvVariable("TYPEORM_DRIVER_TYPE"),
url: PlatformTools.getEnvVariable("TYPEORM_URL"),
host: PlatformTools.getEnvVariable("TYPEORM_HOST"),
port: PlatformTools.getEnvVariable("TYPEORM_PORT"),
username: PlatformTools.getEnvVariable("TYPEORM_USERNAME"),
password: PlatformTools.getEnvVariable("TYPEORM_PASSWORD"),
database: PlatformTools.getEnvVariable("TYPEORM_DATABASE"),
sid: PlatformTools.getEnvVariable("TYPEORM_SID"),
storage: PlatformTools.getEnvVariable("TYPEORM_STORAGE"),
usePool: PlatformTools.getEnvVariable("TYPEORM_USE_POOL") !== undefined ? OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_USE_POOL")) : undefined, // special check for defined is required here
extra: PlatformTools.getEnvVariable("TYPEORM_DRIVER_EXTRA") ? JSON.parse(PlatformTools.getEnvVariable("TYPEORM_DRIVER_EXTRA")) : undefined
},
autoSchemaSync: OrmUtils.toBoolean(process.env.TYPEORM_AUTO_SCHEMA_SYNC),
entities: process.env.TYPEORM_ENTITIES ? process.env.TYPEORM_ENTITIES.split(",") : [],
subscribers: process.env.TYPEORM_SUBSCRIBERS ? process.env.TYPEORM_SUBSCRIBERS.split(",") : [],
entitySchemas: process.env.TYPEORM_ENTITY_SCHEMAS ? process.env.TYPEORM_ENTITY_SCHEMAS.split(",") : [],
namingStrategies: process.env.TYPEORM_NAMING_STRATEGIES ? process.env.TYPEORM_NAMING_STRATEGIES.split(",") : [],
usedNamingStrategy: process.env.TYPEORM_USED_NAMING_STRATEGY,
autoSchemaSync: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_AUTO_SCHEMA_SYNC")),
entities: PlatformTools.getEnvVariable("TYPEORM_ENTITIES") ? PlatformTools.getEnvVariable("TYPEORM_ENTITIES").split(",") : [],
subscribers: PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS") ? PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS").split(",") : [],
entitySchemas: PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS") ? PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS").split(",") : [],
namingStrategies: PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES") ? PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES").split(",") : [],
usedNamingStrategy: PlatformTools.getEnvVariable("TYPEORM_USED_NAMING_STRATEGY"),
logging: {
logQueries: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_QUERIES),
logFailedQueryError: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_FAILED_QUERIES),
logOnlyFailedQueries: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_ONLY_FAILED_QUERIES),
logQueries: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_QUERIES")),
logFailedQueryError: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_FAILED_QUERIES")),
logOnlyFailedQueries: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_ONLY_FAILED_QUERIES")),
}
});
}
@ -349,12 +349,12 @@ export class ConnectionManager {
* If path is not given, then ormconfig.json file will be searched near node_modules directory.
*/
protected async createFromConfigAndConnectToAll(path?: string): Promise<Connection[]> {
const optionsArray: ConnectionOptions[] = require(path || (require("app-root-path").path + "/ormconfig.json"));
const optionsArray: ConnectionOptions[] = PlatformTools.load(path || (PlatformTools.load("app-root-path").path + "/ormconfig.json"));
if (!optionsArray)
throw new Error(`Configuration ${path || "ormconfig.json"} was not found. Add connection configuration inside ormconfig.json file.`);
const promises = optionsArray
.filter(options => !options.environment || options.environment === process.env.NODE_ENV) // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")) // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
.map(options => this.createAndConnectByConnectionOptions(options));
return Promise.all(promises);
@ -367,15 +367,15 @@ export class ConnectionManager {
* If path is not given, then ormconfig.json file will be searched near node_modules directory.
*/
protected async createFromConfigAndConnect(connectionName: string, path?: string): Promise<Connection> {
const optionsArray: ConnectionOptions[] = require(path || (require("app-root-path").path + "/ormconfig.json"));
const optionsArray: ConnectionOptions[] = PlatformTools.load(path || (PlatformTools.load("app-root-path").path + "/ormconfig.json"));
if (!optionsArray)
throw new Error(`Configuration ${path || "ormconfig.json"} was not found. Add connection configuration inside ormconfig.json file.`);
const environmentLessOptions = optionsArray.filter(options => (options.name || "default") === connectionName);
const options = environmentLessOptions.filter(options => !options.environment || options.environment === process.env.NODE_ENV); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
const options = environmentLessOptions.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
if (!options.length)
throw new Error(`Connection "${connectionName}" ${process.env.NODE_ENV ? "for the environment " + process.env.NODE_ENV + " " : ""}was not found in the json configuration file.` +
throw new Error(`Connection "${connectionName}" ${PlatformTools.getEnvVariable("NODE_ENV") ? "for the environment " + PlatformTools.getEnvVariable("NODE_ENV") + " " : ""}was not found in the json configuration file.` +
(environmentLessOptions.length ? ` However there are such configurations for other environments: ${environmentLessOptions.map(options => options.environment).join(", ")}.` : ""));
return this.createAndConnectByConnectionOptions(options[0]);
@ -391,11 +391,11 @@ export class ConnectionManager {
await connection.connect();
// if option is set - drop schema once connection is done
if (options.dropSchemaOnConnection && !process.env.SKIP_SCHEMA_CREATION)
if (options.dropSchemaOnConnection && !PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))
await connection.dropDatabase();
// if option is set - automatically synchronize a schema
if (options.autoSchemaSync && !process.env.SKIP_SCHEMA_CREATION)
if (options.autoSchemaSync && !PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))
await connection.syncSchema();
return connection;
@ -417,9 +417,7 @@ export class ConnectionManager {
protected createDriver(options: DriverOptions, logger: Logger): Driver {
switch (options.type) {
case "mysql":
return new MysqlDriver(options, logger, undefined, "mysql");
case "mysql2":
return new MysqlDriver(options, logger, undefined, "mysql2");
return new MysqlDriver(options, logger, undefined);
case "postgres":
return new PostgresDriver(options, logger);
case "mariadb":

View File

@ -6,7 +6,7 @@ export class MissingDriverError extends Error {
constructor(driverType: string) {
super();
this.message = `Wrong driver ${driverType} given. Supported drivers are: "mysql", "mysql2", "postgres", "mssql", "oracle", "mariadb", "sqlite".`;
this.message = `Wrong driver ${driverType} given. Supported drivers are: "mysql", "postgres", "mssql", "oracle", "mariadb", "sqlite".`;
this.stack = new Error().stack;
}

View File

@ -6,7 +6,7 @@ export interface DriverOptions {
/**
* Database type. This value is required.
*/
readonly type: "mysql"|"mysql2"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql";
readonly type: "mysql"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql";
/**
* Connection url to where perform connection to.

View File

@ -50,16 +50,21 @@ export class DriverUtils {
* Extracts connection data from the connection url.
*/
private static parseConnectionUrl(url: string) {
const urlParser = require("url");
const params = urlParser.parse(url);
const auth = params.auth.split(":");
const firstSlashes = url.indexOf("//");
const preBase = url.substr(firstSlashes + 2);
const secondSlash = preBase.indexOf("/");
const base = (secondSlash !== -1) ? preBase.substr(0, secondSlash) : preBase;
const afterBase = (secondSlash !== -1) ? preBase.substr(secondSlash) + 1 : undefined;
const [usernameAndPassword, hostAndPort] = base.split("@");
const [username, password] = usernameAndPassword.split(":");
const [host, port] = hostAndPort.split(":");
return {
host: params.hostname,
username: auth[0],
password: auth[1],
port: parseInt(params.port),
database: params.pathname.split("/")[1]
host: host,
username: username,
password: password,
port: port ? parseInt(port) : undefined,
database: afterBase ? afterBase.split("/")[0] : undefined
};
}

View File

@ -3,7 +3,6 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
import {DriverOptions} from "../DriverOptions";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
import {DriverUtils} from "../DriverUtils";
import {Logger} from "../../logger/Logger";
import {QueryRunner} from "../../query-runner/QueryRunner";
@ -13,6 +12,7 @@ import {ObjectLiteral} from "../../common/ObjectLiteral";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
/**
* Organizes communication with MySQL DBMS.
@ -57,21 +57,15 @@ export class MysqlDriver implements Driver {
*/
protected logger: Logger;
/**
* Driver type's version. node-mysql and mysql2 are supported.
*/
protected version: "mysql"|"mysql2" = "mysql";
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DriverOptions, logger: Logger, mysql?: any, mysqlVersion: "mysql"|"mysql2" = "mysql") {
constructor(options: DriverOptions, logger: Logger, mysql?: any) {
this.options = DriverUtils.buildDriverOptions(options);
this.logger = logger;
this.mysql = mysql;
this.version = mysqlVersion;
// validate options to make sure everything is set
if (!this.options.host)
@ -130,7 +124,7 @@ export class MysqlDriver implements Driver {
*/
disconnect(): Promise<void> {
if (!this.databaseConnection && !this.pool)
throw new ConnectionIsNotSetError(this.version);
throw new ConnectionIsNotSetError("mysql");
return new Promise<void>((ok, fail) => {
const handler = (err: any) => err ? fail(err) : ok();
@ -155,7 +149,7 @@ export class MysqlDriver implements Driver {
*/
async createQueryRunner(): Promise<QueryRunner> {
if (!this.databaseConnection && !this.pool)
return Promise.reject(new ConnectionIsNotSetError(this.version));
return Promise.reject(new ConnectionIsNotSetError("mysql"));
const databaseConnection = await this.retrieveDatabaseConnection();
return new MysqlQueryRunner(databaseConnection, this, this.logger);
@ -312,20 +306,23 @@ export class MysqlDriver implements Driver {
if (this.databaseConnection)
return Promise.resolve(this.databaseConnection);
throw new ConnectionIsNotSetError(this.version);
throw new ConnectionIsNotSetError("mysql");
}
/**
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (!require)
throw new DriverPackageLoadError();
try {
this.mysql = require(this.version);
this.mysql = PlatformTools.load("mysql"); // try to load first supported package
} catch (e) {
throw new DriverPackageNotInstalledError("Mysql", this.version);
try {
this.mysql = PlatformTools.load("mysql2"); // try to load second supported package
} catch (e) {
throw new DriverPackageNotInstalledError("Mysql", "mysql");
}
}
}

View File

@ -3,7 +3,6 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
import {DriverOptions} from "../DriverOptions";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
import {DriverUtils} from "../DriverUtils";
import {Logger} from "../../logger/Logger";
import {QueryRunner} from "../../query-runner/QueryRunner";
@ -13,6 +12,7 @@ import {ObjectLiteral} from "../../common/ObjectLiteral";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
/**
* Organizes communication with Oracle DBMS.
@ -330,12 +330,10 @@ export class OracleDriver implements Driver {
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (!require)
throw new DriverPackageLoadError();
try {
this.oracle = require("oracledb");
} catch (e) {
this.oracle = PlatformTools.load("oracledb");
} catch (e) { // todo: better error for browser env
throw new DriverPackageNotInstalledError("Oracle", "oracledb");
}
}

View File

@ -4,7 +4,6 @@ import {DriverOptions} from "../DriverOptions";
import {ObjectLiteral} from "../../common/ObjectLiteral";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
import {DriverUtils} from "../DriverUtils";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
@ -13,6 +12,7 @@ import {PostgresQueryRunner} from "./PostgresQueryRunner";
import {QueryRunner} from "../../query-runner/QueryRunner";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
// todo(tests):
// check connection with url
@ -338,12 +338,10 @@ export class PostgresDriver implements Driver {
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (!require)
throw new DriverPackageLoadError();
try {
this.postgres = require("pg");
} catch (e) {
this.postgres = PlatformTools.load("pg");
} catch (e) { // todo: better error for browser env
throw new DriverPackageNotInstalledError("Postgres", "pg");
}
}

View File

@ -4,7 +4,6 @@ import {DriverOptions} from "../DriverOptions";
import {ObjectLiteral} from "../../common/ObjectLiteral";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {Logger} from "../../logger/Logger";
@ -12,6 +11,7 @@ import {SqliteQueryRunner} from "./SqliteQueryRunner";
import {QueryRunner} from "../../query-runner/QueryRunner";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
/**
* Organizes communication with sqlite DBMS.
@ -255,12 +255,10 @@ export class SqliteDriver implements Driver {
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (!require)
throw new DriverPackageLoadError();
try {
this.sqlite = require("sqlite3").verbose();
} catch (e) {
this.sqlite = PlatformTools.load("sqlite3").verbose();
} catch (e) { // todo: better error for browser env
throw new DriverPackageNotInstalledError("SQLite", "sqlite3");
}
}

View File

@ -3,7 +3,6 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
import {DriverOptions} from "../DriverOptions";
import {DatabaseConnection} from "../DatabaseConnection";
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
import {DriverUtils} from "../DriverUtils";
import {Logger} from "../../logger/Logger";
import {QueryRunner} from "../../query-runner/QueryRunner";
@ -13,6 +12,7 @@ import {ObjectLiteral} from "../../common/ObjectLiteral";
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
import {PlatformTools} from "../../platform/PlatformTools";
/**
* Organizes communication with SQL Server DBMS.
@ -315,12 +315,10 @@ export class SqlServerDriver implements Driver {
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (!require)
throw new DriverPackageLoadError();
try {
this.mssql = require("mssql");
} catch (e) {
this.mssql = PlatformTools.load("mssql");
} catch (e) { // todo: better error for browser env
throw new DriverPackageNotInstalledError("SQL Server", "mssql");
}
}

View File

@ -1,6 +1,5 @@
/*!
*/
import {DriverOptions} from "./driver/DriverOptions";
import {ConnectionManager} from "./connection/ConnectionManager";
import {Connection} from "./connection/Connection";
@ -113,7 +112,7 @@ export function getConnectionManager(): ConnectionManager {
* it will try to create connection from environment variables.
* There are several environment variables you can set:
*
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_URL - database connection url. Should be a string.
* - TYPEORM_HOST - database host. Should be a string.
* - TYPEORM_PORT - database access port. Should be a number.
@ -164,7 +163,7 @@ export function createConnection(optionsOrConnectionNameFromConfig?: ConnectionO
* it will try to create connection from environment variables.
* There are several environment variables you can set:
*
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
* - TYPEORM_URL - database connection url. Should be a string.
* - TYPEORM_HOST - database host. Should be a string.
* - TYPEORM_PORT - database access port. Should be a number.

View File

@ -7,6 +7,7 @@ import {MissingJoinTableError} from "./error/MissingJoinTableError";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {MissingPrimaryColumnError} from "./error/MissingPrimaryColumnError";
import {CircularRelationsError} from "./error/CircularRelationsError";
import {DepGraph} from "../util/DepGraph";
/// todo: add check if there are multiple tables with the same name
/// todo: add checks when generated column / table names are too long for the specific driver
@ -112,7 +113,6 @@ export class EntityMetadataValidator {
*/
protected validateDependencies(entityMetadatas: EntityMetadata[]) {
const DepGraph = require("dependency-graph").DepGraph;
const graph = new DepGraph();
entityMetadatas.forEach(entityMetadata => {
graph.addNode(entityMetadata.name);

View File

@ -1,5 +1,6 @@
import {NamingStrategyInterface} from "./NamingStrategyInterface";
import * as _ from "lodash";
import {RandomGenerator} from "../util/RandomGenerator";
import {snakeCase, camelCase} from "../util/StringUtils";
/**
* Naming strategy that is used by default.
@ -7,7 +8,7 @@ import * as _ from "lodash";
export class DefaultNamingStrategy implements NamingStrategyInterface {
tableName(className: string, customName: string): string {
return customName ? customName : _.snakeCase(className);
return customName ? customName : snakeCase(className);
}
columnName(propertyName: string, customName: string): string {
@ -15,7 +16,7 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
}
embeddedColumnName(embeddedPropertyName: string, columnPropertyName: string, columnCustomName?: string): string {
return _.camelCase(embeddedPropertyName + "_" + (columnCustomName ? columnCustomName : columnPropertyName));
return camelCase(embeddedPropertyName + "_" + (columnCustomName ? columnCustomName : columnPropertyName));
}
relationName(propertyName: string): string {
@ -27,7 +28,7 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
return customName;
const key = "ind_" + tableName + "_" + columns.join("_");
return "ind_" + require("sha1")(key);
return "ind_" + RandomGenerator.sha1(key).substr(0, 27);
}
joinColumnInverseSideName(joinColumnName: string, propertyName: string): string {
@ -43,18 +44,18 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
secondPropertyName: string,
firstColumnName: string,
secondColumnName: string): string {
return _.snakeCase(firstTableName + "_" + firstPropertyName + "_" + secondTableName + "_" + secondColumnName);
return snakeCase(firstTableName + "_" + firstPropertyName + "_" + secondTableName + "_" + secondColumnName);
}
joinTableColumnName(tableName: string, columnName: string, secondTableName: string, secondColumnName: string): string {
const column1 = _.camelCase(tableName + "_" + columnName);
const column2 = _.camelCase(secondTableName + "_" + secondColumnName);
const column1 = camelCase(tableName + "_" + columnName);
const column2 = camelCase(secondTableName + "_" + secondColumnName);
return column1 === column2 ? column1 + "_1" : column1; // todo: do we still need _1 prefix?!
}
joinTableInverseColumnName(tableName: string, columnName: string, secondTableName: string, secondColumnName: string): string {
const column1 = _.camelCase(tableName + "_" + columnName);
const column2 = _.camelCase(secondTableName + "_" + secondColumnName);
const column1 = camelCase(tableName + "_" + columnName);
const column2 = camelCase(secondTableName + "_" + secondColumnName);
return column1 === column2 ? column1 + "_2" : column1; // todo: do we still need _2 prefix?!
}
@ -64,11 +65,11 @@ export class DefaultNamingStrategy implements NamingStrategyInterface {
foreignKeyName(tableName: string, columnNames: string[], referencedTableName: string, referencedColumnNames: string[]): string {
const key = `${tableName}_${columnNames.join("_")}_${referencedTableName}_${referencedColumnNames.join("_")}`;
return "fk_" + require("sha1")(key).substr(0, 27); // todo: use crypto instead?
return "fk_" + RandomGenerator.sha1(key).substr(0, 27); // todo: use crypto instead?
}
classTableInheritanceParentColumnName(parentTableName: any, parentTableIdPropertyName: any): string {
return _.camelCase(parentTableName + "_" + parentTableIdPropertyName);
return camelCase(parentTableName + "_" + parentTableIdPropertyName);
}
/**

View File

@ -0,0 +1,23 @@
/**
* Browser's implementation of the platform-specific tools.
*
* This file gonna replace PlatformTools for browser environment.
* For node.js environment this class is not getting packaged.
* Don't use methods of this class in the code, use PlatformTools methods instead.
*/
export class BrowserPlatformTools {
/**
* Type of the currently running platform.
*/
static type: "browser"|"node" = "browser";
/**
* Loads ("require"-s) given file or package.
* This operation only supports on node platform
*/
static load(name: string) {
throw new Error(`This option/function is not supported in the browser environment. Failed operation: require("${name}").`);
}
}

View File

@ -0,0 +1,57 @@
import * as path from "path";
import * as fs from "fs";
/**
* Platform-specific tools.
*/
export class PlatformTools {
/**
* Type of the currently running platform.
*/
static type: "browser"|"node" = "node";
/**
* Loads ("require"-s) given file or package.
* This operation only supports on node platform
*/
static load(name: string): any {
return require(name);
}
/**
* Normalizes given path. Does "path.normalize".
*/
static pathNormilize(pathStr: string): string {
return path.normalize(pathStr);
}
/**
* Gets file extension. Does "path.extname".
*/
static pathExtname(pathStr: string): string {
return path.extname(pathStr);
}
/**
* Resolved given path. Does "path.resolve".
*/
static pathResolve(pathStr: string): string {
return path.resolve(pathStr);
}
/**
* Synchronously checks if file exist. Does "fs.existsSync".
*/
static fileExist(pathStr: string): boolean {
return fs.existsSync(pathStr);
}
/**
* Gets environment variable.
*/
static getEnvVariable(name: string): any {
return process.env[name];
}
}

232
src/util/DepGraph.ts Normal file
View File

@ -0,0 +1,232 @@
/**
* This source code is from https://github.com/jriecken/dependency-graph
* Just added "any" types here, wrapper everything into exported class.
* We cant use a package itself because we want to package "everything-in-it" for the frontend users of TypeORM.
*/
/**
* A simple dependency graph
*/
/**
* Helper for creating a Depth-First-Search on
* a set of edges.
*
* Detects cycles and throws an Error if one is detected.
*
* @param edges The set of edges to DFS through
* @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges)
* @param result An array in which the results will be populated
*/
function createDFS(edges: any, leavesOnly: any, result: any) {
let currentPath: any[] = [];
let visited: any = {};
return function DFS(currentNode: any) {
visited[currentNode] = true;
currentPath.push(currentNode);
edges[currentNode].forEach(function (node: any) {
if (!visited[node]) {
DFS(node);
} else if (currentPath.indexOf(node) >= 0) {
currentPath.push(node);
throw new Error(`Dependency Cycle Found: ${currentPath.join(" -> ")}`);
}
});
currentPath.pop();
if ((!leavesOnly || edges[currentNode].length === 0) && result.indexOf(currentNode) === -1) {
result.push(currentNode);
}
};
}
export class DepGraph {
nodes: any = {};
outgoingEdges: any = {}; // Node -> [Dependency Node]
incomingEdges: any = {}; // Node -> [Dependant Node]
/**
* Add a node to the dependency graph. If a node already exists, this method will do nothing.
*/
addNode(node: any, data?: any) {
if (!this.hasNode(node)) {
// Checking the arguments length allows the user to add a node with undefined data
if (arguments.length === 2) {
this.nodes[node] = data;
} else {
this.nodes[node] = node;
}
this.outgoingEdges[node] = [];
this.incomingEdges[node] = [];
}
}
/**
* Remove a node from the dependency graph. If a node does not exist, this method will do nothing.
*/
removeNode(node: any) {
if (this.hasNode(node)) {
delete this.nodes[node];
delete this.outgoingEdges[node];
delete this.incomingEdges[node];
[this.incomingEdges, this.outgoingEdges].forEach(function(edgeList) {
Object.keys(edgeList).forEach(function(key: any) {
let idx = edgeList[key].indexOf(node);
if (idx >= 0) {
edgeList[key].splice(idx, 1);
}
}, this);
});
}
}
/**
* Check if a node exists in the graph
*/
hasNode(node: any) {
return this.nodes.hasOwnProperty(node);
}
/**
* Get the data associated with a node name
*/
getNodeData(node: any) {
if (this.hasNode(node)) {
return this.nodes[node];
} else {
throw new Error(`Node does not exist: ${node}`);
}
}
/**
* Set the associated data for a given node name. If the node does not exist, this method will throw an error
*/
setNodeData(node: any, data: any) {
if (this.hasNode(node)) {
this.nodes[node] = data;
} else {
throw new Error(`Node does not exist: ${node}`);
}
}
/**
* Add a dependency between two nodes. If either of the nodes does not exist,
* an Error will be thrown.
*/
addDependency(from: any, to: any) {
if (!this.hasNode(from)) {
throw new Error(`Node does not exist: ${from}`);
}
if (!this.hasNode(to)) {
throw new Error(`Node does not exist: ${to}`);
}
if (this.outgoingEdges[from].indexOf(to) === -1) {
this.outgoingEdges[from].push(to);
}
if (this.incomingEdges[to].indexOf(from) === -1) {
this.incomingEdges[to].push(from);
}
return true;
}
/**
* Remove a dependency between two nodes.
*/
removeDependency(from: any, to: any) {
let idx: any;
if (this.hasNode(from)) {
idx = this.outgoingEdges[from].indexOf(to);
if (idx >= 0) {
this.outgoingEdges[from].splice(idx, 1);
}
}
if (this.hasNode(to)) {
idx = this.incomingEdges[to].indexOf(from);
if (idx >= 0) {
this.incomingEdges[to].splice(idx, 1);
}
}
}
/**
* Get an array containing the nodes that the specified node depends on (transitively).
*
* Throws an Error if the graph has a cycle, or the specified node does not exist.
*
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned
* in the array.
*/
dependenciesOf(node: any, leavesOnly: any) {
if (this.hasNode(node)) {
let result: any[] = [];
let DFS = createDFS(this.outgoingEdges, leavesOnly, result);
DFS(node);
let idx = result.indexOf(node);
if (idx >= 0) {
result.splice(idx, 1);
}
return result;
}
else {
throw new Error(`Node does not exist: ${node}`);
}
}
/**
* get an array containing the nodes that depend on the specified node (transitively).
*
* Throws an Error if the graph has a cycle, or the specified node does not exist.
*
* If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array.
*/
dependantsOf(node: any, leavesOnly: any) {
if (this.hasNode(node)) {
let result: any[] = [];
let DFS = createDFS(this.incomingEdges, leavesOnly, result);
DFS(node);
let idx = result.indexOf(node);
if (idx >= 0) {
result.splice(idx, 1);
}
return result;
} else {
throw new Error(`Node does not exist: ${node}`);
}
}
/**
* Construct the overall processing order for the dependency graph.
*
* Throws an Error if the graph has a cycle.
*
* If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned.
*/
overallOrder(leavesOnly?: any) {
let self = this;
let result: any[] = [];
let keys = Object.keys(this.nodes);
if (keys.length === 0) {
return result; // Empty graph
} else {
// Look for cycles - we run the DFS starting at all the nodes in case there
// are several disconnected subgraphs inside this dependency graph.
let CycleDFS = createDFS(this.outgoingEdges, false, []);
keys.forEach(function(n: any) {
CycleDFS(n);
});
let DFS = createDFS(this.outgoingEdges, leavesOnly, result);
// Find all potential starting points (nodes with nothing depending on them) an
// run a DFS starting at these points to get the order
keys.filter(function (node) {
return self.incomingEdges[node].length === 0;
}).forEach(function (n) {
DFS(n);
});
return result;
}
}
}

View File

@ -1,4 +1,4 @@
import * as path from "path";
import {PlatformTools} from "../platform/PlatformTools";
/**
* Loads all exported classes from the given directory.
@ -20,26 +20,29 @@ export function importClassesFromDirectories(directories: string[], formats = ["
}
const allFiles = directories.reduce((allDirs, dir) => {
return allDirs.concat(require("glob").sync(path.normalize(dir)));
return allDirs.concat(PlatformTools.load("glob").sync(PlatformTools.pathNormilize(dir)));
}, [] as string[]);
const dirs = allFiles
.filter(file => {
const dtsExtension = file.substring(file.length - 5, file.length);
return formats.indexOf(path.extname(file)) !== -1 && dtsExtension !== ".d.ts";
return formats.indexOf(PlatformTools.pathExtname(file)) !== -1 && dtsExtension !== ".d.ts";
})
.map(file => require(path.resolve(file)));
.map(file => PlatformTools.load(PlatformTools.pathResolve(file)));
return loadFileClasses(dirs, []);
}
/**
* Loads all json files from the given directory.
*/
export function importJsonsFromDirectories(directories: string[], format = ".json"): any[] {
const allFiles = directories.reduce((allDirs, dir) => {
return allDirs.concat(require("glob").sync(path.normalize(dir)));
return allDirs.concat(PlatformTools.load("glob").sync(PlatformTools.pathNormilize(dir)));
}, [] as string[]);
return allFiles
.filter(file => path.extname(file) === format)
.map(file => require(path.resolve(file)));
.filter(file => PlatformTools.pathExtname(file) === format)
.map(file => PlatformTools.load(PlatformTools.pathResolve(file)));
}

146
src/util/RandomGenerator.ts Normal file
View File

@ -0,0 +1,146 @@
export class RandomGenerator {
/**
* discuss at: http://locutus.io/php/sha1/
* original by: Webtoolkit.info (http://www.webtoolkit.info/)
* improved by: Michael White (http://getsprink.com)
* improved by: Kevin van Zonneveld (http://kvz.io)
* input by: Brett Zamir (http://brett-zamir.me)
* note 1: Keep in mind that in accordance with PHP, the whole string is buffered and then
* note 1: hashed. If available, we'd recommend using Node's native crypto modules directly
* note 1: in a steaming fashion for faster and more efficient hashing
* example 1: sha1('Kevin van Zonneveld')
* returns 1: '54916d2e62f65b3afa6e192e6a601cdbe5cb5897'
*/
static sha1(str: string) {
let _rotLeft = function(n: any, s: any) {
let t4 = (n << s) | (n >>> (32 - s));
return t4;
};
let _cvtHex = function(val: any) {
let str = "";
let i;
let v;
for (i = 7; i >= 0; i--) {
v = (val >>> (i * 4)) & 0x0f;
str += v.toString(16);
}
return str;
};
let blockstart;
let i, j;
let W = new Array(80);
let H0 = 0x67452301;
let H1 = 0xEFCDAB89;
let H2 = 0x98BADCFE;
let H3 = 0x10325476;
let H4 = 0xC3D2E1F0;
let A, B, C, D, E;
let temp;
// utf8_encode
str = /*unescape*/(encodeURIComponent(str));
let strLen = str.length;
let wordArray = [];
for (i = 0; i < strLen - 3; i += 4) {
j = str.charCodeAt(i) << 24 |
str.charCodeAt(i + 1) << 16 |
str.charCodeAt(i + 2) << 8 |
str.charCodeAt(i + 3);
wordArray.push(j);
}
switch (strLen % 4) {
case 0:
i = 0x080000000;
break;
case 1:
i = str.charCodeAt(strLen - 1) << 24 | 0x0800000;
break;
case 2:
i = str.charCodeAt(strLen - 2) << 24 | str.charCodeAt(strLen - 1) << 16 | 0x08000;
break;
case 3:
i = str.charCodeAt(strLen - 3) << 24 |
str.charCodeAt(strLen - 2) << 16 |
str.charCodeAt(strLen - 1) <<
8 | 0x80;
break;
}
wordArray.push(i);
while ((wordArray.length % 16) !== 14) {
wordArray.push(0);
}
wordArray.push(strLen >>> 29);
wordArray.push((strLen << 3) & 0x0ffffffff);
for (blockstart = 0; blockstart < wordArray.length; blockstart += 16) {
for (i = 0; i < 16; i++) {
W[i] = wordArray[blockstart + i];
}
for (i = 16; i <= 79; i++) {
W[i] = _rotLeft(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
}
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for (i = 0; i <= 19; i++) {
temp = (_rotLeft(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
E = D;
D = C;
C = _rotLeft(B, 30);
B = A;
A = temp;
}
for (i = 20; i <= 39; i++) {
temp = (_rotLeft(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
E = D;
D = C;
C = _rotLeft(B, 30);
B = A;
A = temp;
}
for (i = 40; i <= 59; i++) {
temp = (_rotLeft(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
E = D;
D = C;
C = _rotLeft(B, 30);
B = A;
A = temp;
}
for (i = 60; i <= 79; i++) {
temp = (_rotLeft(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
E = D;
D = C;
C = _rotLeft(B, 30);
B = A;
A = temp;
}
H0 = (H0 + A) & 0x0ffffffff;
H1 = (H1 + B) & 0x0ffffffff;
H2 = (H2 + C) & 0x0ffffffff;
H3 = (H3 + D) & 0x0ffffffff;
H4 = (H4 + E) & 0x0ffffffff;
}
temp = _cvtHex(H0) + _cvtHex(H1) + _cvtHex(H2) + _cvtHex(H3) + _cvtHex(H4);
return temp.toLowerCase();
}
}

19
src/util/StringUtils.ts Normal file
View File

@ -0,0 +1,19 @@
/**
* Converts string into camelCase.
*
* @see http://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
*/
export function camelCase(str: string) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
}).replace(/\s+/g, "");
}
/**
* Converts string into snake-case.
*
* @see http://stackoverflow.com/questions/30521224/javascript-convert-pascalcase-to-underscore-case
*/
export function snakeCase(str: string) {
return str.replace(/(?:^|\.?)([A-Z])/g, (x, y) => "_" + y.toLowerCase()).replace(/^_/, "");
}