Merge branch 'master' into greenkeeper/@types/mysql-2.15.5

This commit is contained in:
Kononnable 2018-06-17 23:23:11 +02:00 committed by GitHub
commit 5f276fe776
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 2100 additions and 1726 deletions

View File

@ -1,6 +1,6 @@
language: node_js
node_js:
#- stable
- stable
- 8
- 6
sudo: required
@ -8,7 +8,7 @@ services:
- docker
env:
matrix:
- POSTGRES_Skip=0 POSTGRES_Host=localhost POSTGRES_Port=5432 POSTGRES_Username=postgres
- POSTGRES_Skip=1 POSTGRES_Host=localhost POSTGRES_Port=5432 POSTGRES_Username=postgres
POSTGRES_Password=!Passw0rd POSTGRES_Database=typeorm_mg POSTGRES_SSL=0 MYSQL_Skip=0
MYSQL_Host=localhost MYSQL_Port=3306 MYSQL_Username=root MYSQL_Password=!Passw0rd
MYSQL_Database=typeorm_mg MYSQL_SSL=1 MARIADB_Skip=0 MARIADB_Host=localhost MARIADB_Port=3307
@ -33,17 +33,21 @@ before_install:
- if [ -z "$DOCKER_USERNAME" ]; then mv docker-compose-without-login.yml docker-compose.yml; fi
- if [ -z "$DOCKER_USERNAME" ]; then export ORACLE_Skip=1; fi
- docker-compose up -d
- mkdir /opt/oracle
- if [ -n "$DOCKER_USERNAME" ]; then docker cp typeorm-mg-oracle-client:/usr/lib/oracle/12.2/client64/lib /opt/oracle/instantclient_12_2; fi
- export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2:$LD_LIBRARY_PATH
- npm install -g npm@5
- npm install -g greenkeeper-lockfile@1
- mkdir /opt/oracle
install:
- case $TRAVIS_BRANCH in greenkeeper*) npm i;; *) npm ci;; esac;
before_script:
- if [ -n "$DOCKER_USERNAME" ]; then npm i oracledb --no-save; docker cp typeorm-mg-oracle-client:/usr/lib/oracle/12.2/client64/lib /opt/oracle/instantclient_12_2; fi
- export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2:$LD_LIBRARY_PATH
- greenkeeper-lockfile-update
- typings install
- npm link typescript
- tsc
- sleep 180
- docker logs typeorm-mg-postgres
after_script:
- greenkeeper-lockfile-upload
dd:

View File

@ -21,6 +21,9 @@ To install module globally simply type `npm i -g typeorm-model-generator` in you
### Npx way
Thanks to npx you can use npm modules without polluting global installs. So nothing to do here :)
>To use `npx` you need to use npm at version at least 5.2.0. Try updating your npm by `npm i -g npm`
### Database drivers
All database drivers except oracle are installed by default. To use typeorm-model-generator with oracle databese you need to install driver with `npm i oracledb` and configure [oracle install client](http://www.oracle.com/technetwork/database/database-technologies/instant-client/overview/index.html) on your machine.
## Usage
```shell
@ -53,6 +56,8 @@ Options:
--cp, --case-property Convert property names to specified case
[choices: "pascal", "camel", "none"] [default: "none"]
--lazy Generate lazy relations [boolean] [default: false]
--namingStrategy Use custom naming strategy
--relationIds Generate RelationId fields [boolean] [default: false]
--generateConstructor Generate constructor allowing partial initialization
[boolean] [default: false]
```
@ -85,3 +90,7 @@ Options:
```
npx typeorm-model-generator -d "Z:\sqlite.db" -e sqlite -o .
````
## Naming strategy
If you want to generate custom names for properties in generated entities you need to use custom naming strategy. You need to create your own version of [NamingStrategy](https://github.com/Kononnable/typeorm-model-generator/blob/master/src/NamingStrategy.ts) and pass it as command parameter.
```typeorm-model-generator -d typeorm_mg --namingStrategy=./NamingStrategy -e sqlite -db /tmp/sqliteto.db```

9
changelog.md Normal file
View File

@ -0,0 +1,9 @@
# Changelog
## 0.2.17
* added support for relationId fields
* added support for custom naming entity fields
* removed oracledb from dependencies
* generating nullable column types for nullable columns

View File

@ -24,6 +24,7 @@ gulp.task('clean', function () {
gulp.task('prettier', function () {
return gulp.src('.prettierrc')
.pipe(shell(['prettier ./src/**/*.ts --write']))
.pipe(shell(['prettier ./src/*.ts --write']))
});
gulp.task('pre-commit', ['prettier'], function () {

3124
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "typeorm-model-generator",
"version": "0.2.16",
"version": "0.2.17",
"description": "Generates models for TypeORM from existing databases.",
"bin": "bin/typeorm-model-generator",
"scripts": {
@ -25,7 +25,6 @@
"handlebars": "^4.0.11",
"mssql": "^4.0.4",
"mysql": "^2.15.0",
"oracledb": "^2.2.0",
"pg": "^7.4.0",
"reflect-metadata": "^0.1.10",
"typeorm": "^0.2.4",

View File

@ -0,0 +1,14 @@
import { RelationInfo } from "./models/RelationInfo";
import { DatabaseModel } from "./models/DatabaseModel";
export abstract class AbstractNamingStrategy {
abstract relationName(
columnName: string,
relation: RelationInfo,
dbModel: DatabaseModel
): string;
abstract entityName(entityName: string): string;
abstract columnName(columnName: string): string;
}

View File

@ -5,6 +5,7 @@ import fs = require("fs");
import path = require("path");
import * as TomgUtils from "./Utils";
import changeCase = require("change-case");
import { AbstractNamingStrategy } from "./AbstractNamingStrategy";
export class Engine {
constructor(
@ -20,7 +21,9 @@ export class Engine {
this.Options.user,
this.Options.password,
this.Options.schemaName,
this.Options.ssl
this.Options.ssl,
this.Options.namingStrategy,
this.Options.relationIds
);
if (dbModel.entities.length > 0) {
this.createModelFromMetadata(dbModel);
@ -39,7 +42,9 @@ export class Engine {
user: string,
password: string,
schemaName: string,
ssl: boolean
ssl: boolean,
namingStrategy: AbstractNamingStrategy,
relationIds: boolean
): Promise<DatabaseModel> {
return await this.driver.GetDataFromServer(
database,
@ -48,7 +53,9 @@ export class Engine {
user,
password,
schemaName,
ssl
ssl,
namingStrategy,
relationIds
);
}
private createModelFromMetadata(databaseModel: DatabaseModel) {
@ -223,7 +230,7 @@ export class Engine {
"username": "${this.Options.user}",
"password": "${this.Options.password}",
"database": "${this.Options.databaseName}",
"synchronize": false
"synchronize": false,
"entities": [
"entities/*.js"
]
@ -271,4 +278,6 @@ export interface EngineOptions {
convertCaseProperty: "pascal" | "camel" | "none";
lazy: boolean;
constructor: boolean;
namingStrategy: AbstractNamingStrategy;
relationIds: boolean;
}

76
src/NamingStrategy.ts Normal file
View File

@ -0,0 +1,76 @@
import { AbstractNamingStrategy } from "./AbstractNamingStrategy";
import { RelationInfo } from "./models/RelationInfo";
import { DatabaseModel } from "./models/DatabaseModel";
export class NamingStrategy extends AbstractNamingStrategy {
relationName(
columnOldName: string,
relation: RelationInfo,
dbModel: DatabaseModel
): string {
let isRelationToMany = relation.isOneToMany || relation.isManyToMany;
let ownerEntity = dbModel.entities.filter(v => {
return v.EntityName == relation.ownerTable;
})[0];
let referencedEntity = dbModel.entities.filter(v => {
return v.EntityName == relation.relatedTable;
})[0];
let columnName =
columnOldName[0].toLowerCase() +
columnOldName.substring(1, columnOldName.length);
if (
columnName
.toLowerCase()
.endsWith(
"id"
) /*&& !ownerEntity.Columns.some(x=>x.tsName==columnName && x.isPrimary)*/
) {
columnName = columnName.substring(
0,
columnName.toLowerCase().lastIndexOf("id")
);
}
if (!isNaN(parseInt(columnName[columnName.length - 1]))) {
columnName = columnName.substring(0, columnName.length - 1);
}
if (!isNaN(parseInt(columnName[columnName.length - 1]))) {
columnName = columnName.substring(0, columnName.length - 1);
}
columnName += isRelationToMany ? "s" : "";
if (
relation.relationType != "ManyToMany" &&
columnOldName != columnName
) {
if (ownerEntity.Columns.some(v => v.tsName == columnName)) {
columnName = columnName + "_";
for (let i = 2; i <= ownerEntity.Columns.length; i++) {
columnName =
columnName.substring(
0,
columnName.length - i.toString().length
) + i.toString();
if (
ownerEntity.Columns.every(
v =>
v.tsName != columnName ||
columnName == columnOldName
)
)
break;
}
}
}
return columnName;
}
entityName(entityName: string): string {
return entityName;
}
columnName(columnName: string): string {
return columnName;
}
}

View File

@ -1,15 +1,120 @@
import { EntityInfo } from "./../models/EntityInfo";
import { DatabaseModel } from "./../models/DatabaseModel";
import * as TomgUtils from "./../Utils";
import { EntityInfo } from "../models/EntityInfo";
import { DatabaseModel } from "../models/DatabaseModel";
import * as TomgUtils from "../Utils";
import { RelationInfo } from "../models/RelationInfo";
import { ColumnInfo } from "../models/ColumnInfo";
import {
WithWidthColumnType,
WithPrecisionColumnType,
WithLengthColumnType
} from "./../../node_modules/typeorm/driver/types/ColumnTypes";
} from "typeorm/driver/types/ColumnTypes";
import { AbstractNamingStrategy } from "../AbstractNamingStrategy";
export abstract class AbstractDriver {
changeColumnNames(dbModel: DatabaseModel) {
dbModel.entities.forEach(entity => {
entity.Columns.forEach(column => {
let newName = this.namingStrategy.columnName(column.tsName);
entity.Indexes.forEach(index => {
index.columns
.filter(column2 => {
return column2.name == column.tsName;
})
.forEach(column2 => {
column2.name = newName;
});
});
dbModel.entities.forEach(entity2 => {
entity2.Columns.forEach(column2 => {
column2.relations
.filter(relation => {
return (
relation.relatedTable ==
entity.EntityName &&
relation.relatedColumn == column.tsName
);
})
.map(v => {
v.relatedColumn = newName;
});
column2.relations
.filter(relation => {
return (
relation.ownerTable == entity.EntityName &&
relation.ownerColumn == column.tsName
);
})
.map(v => {
v.ownerColumn = newName;
});
});
});
column.tsName = newName;
});
});
}
changeEntityNames(dbModel: DatabaseModel) {
dbModel.entities.forEach(entity => {
let newName = this.namingStrategy.columnName(entity.EntityName);
dbModel.entities.forEach(entity2 => {
entity2.Columns.forEach(column => {
column.relations.forEach(relation => {
if (relation.ownerTable == entity.EntityName)
relation.ownerTable = newName;
if (relation.relatedTable == entity.EntityName)
relation.relatedTable = newName;
});
});
});
entity.EntityName = newName;
});
}
changeRelationNames(dbModel: DatabaseModel) {
dbModel.entities.forEach(entity => {
entity.Columns.forEach(column => {
column.relations.forEach(relation => {
if (true || !relation.isOwner) {
let newName = this.namingStrategy.relationName(
column.tsName,
relation,
dbModel
);
dbModel.entities.forEach(entity2 => {
entity2.Columns.forEach(column2 => {
column2.relations.forEach(relation2 => {
if (
relation2.relatedTable ==
entity.EntityName &&
relation2.ownerColumn == column.tsName
) {
relation2.ownerColumn = newName;
}
if (
relation2.relatedTable ==
entity.EntityName &&
relation2.relatedColumn == column.tsName
) {
relation2.relatedColumn = newName;
}
if (relation.isOwner) {
entity.Indexes.forEach(ind => {
ind.columns.forEach(col => {
if (col.name == column.tsName) {
col.name = newName;
}
});
});
}
});
});
});
column.tsName = newName;
}
});
});
});
}
ColumnTypesWithWidth: WithWidthColumnType[] = [
"tinyint",
"smallint",
@ -52,6 +157,8 @@ export abstract class AbstractDriver {
"binary",
"varbinary"
];
namingStrategy: AbstractNamingStrategy;
generateRelationsIds: boolean;
FindManyToManyRelations(dbModel: DatabaseModel) {
let manyToManyEntities = dbModel.entities.filter(entity => {
@ -79,7 +186,7 @@ export abstract class AbstractDriver {
)[0];
relatedTable1.Columns = relatedTable1.Columns.filter(
v =>
!v.name
!v.tsName
.toLowerCase()
.startsWith(entity.EntityName.toLowerCase())
);
@ -88,7 +195,7 @@ export abstract class AbstractDriver {
)[0];
relatedTable2.Columns = relatedTable2.Columns.filter(
v =>
!v.name
!v.tsName
.toLowerCase()
.startsWith(entity.EntityName.toLowerCase())
);
@ -97,21 +204,26 @@ export abstract class AbstractDriver {
});
let column1 = new ColumnInfo();
column1.name = namesOfRelatedTables[1];
column1.tsName = namesOfRelatedTables[1];
let col1Rel = new RelationInfo();
col1Rel.relatedTable = namesOfRelatedTables[1];
col1Rel.relatedColumn = namesOfRelatedTables[1];
col1Rel.relationType = "ManyToMany";
col1Rel.isOwner = true;
col1Rel.ownerColumn = namesOfRelatedTables[0];
column1.relations.push(col1Rel);
relatedTable1.Columns.push(column1);
let column2 = new ColumnInfo();
column2.name = namesOfRelatedTables[0];
column2.tsName = namesOfRelatedTables[0];
let col2Rel = new RelationInfo();
col2Rel.relatedTable = namesOfRelatedTables[0];
col2Rel.relatedColumn = namesOfRelatedTables[1];
col2Rel.relationType = "ManyToMany";
col2Rel.isOwner = false;
column2.relations.push(col2Rel);
@ -126,9 +238,13 @@ export abstract class AbstractDriver {
user: string,
password: string,
schema: string,
ssl: boolean
ssl: boolean,
namingStrategy: AbstractNamingStrategy,
relationIds: boolean
): Promise<DatabaseModel> {
this.generateRelationsIds = relationIds;
let dbModel = <DatabaseModel>{};
this.namingStrategy = namingStrategy;
await this.ConnectToServer(database, server, port, user, password, ssl);
let sqlEscapedSchema = "'" + schema.split(",").join("','") + "'";
dbModel.entities = await this.GetAllTables(sqlEscapedSchema);
@ -141,8 +257,16 @@ export abstract class AbstractDriver {
await this.DisconnectFromServer();
this.FindManyToManyRelations(dbModel);
this.FindPrimaryColumnsFromIndexes(dbModel);
this.ApplyNamingStrategy(dbModel);
return dbModel;
}
private ApplyNamingStrategy(dbModel: DatabaseModel) {
this.changeRelationNames(dbModel);
this.changeEntityNames(dbModel);
this.changeColumnNames(dbModel);
}
abstract async ConnectToServer(
database: string,
server: string,
@ -209,7 +333,7 @@ export abstract class AbstractDriver {
) {
let ownerColumn = ownerEntity.Columns.find(column => {
return (
column.name ==
column.tsName ==
relationTmp.ownerColumnsNames[relationColumnIndex]
);
});
@ -227,7 +351,7 @@ export abstract class AbstractDriver {
}
let relatedColumn = referencedEntity.Columns.find(column => {
return (
column.name ==
column.tsName ==
relationTmp.referencedColumnsNames[relationColumnIndex]
);
});
@ -249,48 +373,49 @@ export abstract class AbstractDriver {
return (
index.isUnique &&
index.columns.some(col => {
return col.name == ownerColumn!.name;
return col.name == ownerColumn!.tsName;
})
);
});
isOneToMany = !index;
let ownerRelation = new RelationInfo();
let columnName =
ownerEntity.EntityName.toLowerCase() +
(isOneToMany ? "s" : "");
ownerRelation.actionOnDelete = relationTmp.actionOnDelete;
ownerRelation.actionOnUpdate = relationTmp.actionOnUpdate;
ownerRelation.isOwner = true;
ownerRelation.relatedColumn = relatedColumn.tsName.toLowerCase();
ownerRelation.relatedTable = relationTmp.referencedTable;
ownerRelation.ownerTable = relationTmp.ownerTable;
ownerRelation.relationType = isOneToMany
? "ManyToOne"
: "OneToOne";
ownerRelation.relationIdField = this.generateRelationsIds;
let columnName = ownerEntity.EntityName;
if (
referencedEntity.Columns.filter(filterVal => {
return filterVal.name == columnName;
}).length > 0
referencedEntity.Columns.some(v => v.tsName == columnName)
) {
for (let i = 2; i <= ownerEntity.Columns.length; i++) {
columnName = columnName + "_";
for (let i = 2; i <= referencedEntity.Columns.length; i++) {
columnName =
ownerEntity.EntityName.toLowerCase() +
(isOneToMany ? "s" : "") +
i.toString();
columnName.substring(
0,
columnName.length - i.toString().length
) + i.toString();
if (
referencedEntity.Columns.filter(filterVal => {
return filterVal.name == columnName;
}).length == 0
referencedEntity.Columns.every(
v => v.tsName != columnName
)
)
break;
}
}
ownerRelation.actionOnDelete = relationTmp.actionOnDelete;
ownerRelation.actionOnUpdate = relationTmp.actionOnUpdate;
ownerRelation.isOwner = true;
ownerRelation.relatedColumn = relatedColumn.name.toLowerCase();
ownerRelation.relatedTable = relationTmp.referencedTable;
ownerRelation.ownerTable = relationTmp.ownerTable;
ownerRelation.ownerColumn = columnName;
ownerRelation.relationType = isOneToMany
? "ManyToOne"
: "OneToOne";
ownerColumn.relations.push(ownerRelation);
if (isOneToMany) {
let col = new ColumnInfo();
col.name = columnName;
col.tsName = columnName;
let referencedRelation = new RelationInfo();
col.relations.push(referencedRelation);
referencedRelation.actionOnDelete =
@ -298,15 +423,15 @@ export abstract class AbstractDriver {
referencedRelation.actionOnUpdate =
relationTmp.actionOnUpdate;
referencedRelation.isOwner = false;
referencedRelation.relatedColumn = ownerColumn.name;
referencedRelation.relatedColumn = ownerColumn.tsName;
referencedRelation.relatedTable = relationTmp.ownerTable;
referencedRelation.ownerTable = relationTmp.referencedTable;
referencedRelation.ownerColumn = relatedColumn.name.toLowerCase();
referencedRelation.ownerColumn = relatedColumn.tsName;
referencedRelation.relationType = "OneToMany";
referencedEntity.Columns.push(col);
} else {
let col = new ColumnInfo();
col.name = columnName;
col.tsName = columnName;
let referencedRelation = new RelationInfo();
col.relations.push(referencedRelation);
referencedRelation.actionOnDelete =
@ -314,10 +439,10 @@ export abstract class AbstractDriver {
referencedRelation.actionOnUpdate =
relationTmp.actionOnUpdate;
referencedRelation.isOwner = false;
referencedRelation.relatedColumn = ownerColumn.name;
referencedRelation.relatedColumn = ownerColumn.tsName;
referencedRelation.relatedTable = relationTmp.ownerTable;
referencedRelation.ownerTable = relationTmp.referencedTable;
referencedRelation.ownerColumn = relatedColumn.name.toLowerCase();
referencedRelation.ownerColumn = relatedColumn.tsName;
referencedRelation.relationType = "OneToOne";
referencedEntity.Columns.push(col);
}
@ -344,7 +469,9 @@ export abstract class AbstractDriver {
entity.Columns.forEach(col => {
if (
primaryIndex &&
primaryIndex.columns.some(cIndex => cIndex.name == col.name)
primaryIndex.columns.some(
cIndex => cIndex.name == col.tsName
)
)
col.isPrimary = true;
});

View File

@ -1,8 +1,8 @@
import { AbstractDriver } from "./AbstractDriver";
import * as MSSQL from "mssql";
import { ColumnInfo } from "./../models/ColumnInfo";
import { EntityInfo } from "./../models/EntityInfo";
import * as TomgUtils from "./../Utils";
import { ColumnInfo } from "../models/ColumnInfo";
import { EntityInfo } from "../models/EntityInfo";
import * as TomgUtils from "../Utils";
export class MssqlDriver extends AbstractDriver {
GetAllTablesQuery = async (schema: string) => {
@ -53,7 +53,8 @@ export class MssqlDriver extends AbstractDriver {
})
.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp.COLUMN_NAME;
colInfo.tsName = resp.COLUMN_NAME;
colInfo.sqlName = resp.COLUMN_NAME;
colInfo.is_nullable = resp.IS_NULLABLE == "YES";
colInfo.is_generated = resp.IsIdentity == 1;
colInfo.is_unique = resp.IsUnique == 1;

View File

@ -1,8 +1,8 @@
import { AbstractDriver } from "./AbstractDriver";
import * as MYSQL from "mysql";
import { ColumnInfo } from "./../models/ColumnInfo";
import { EntityInfo } from "./../models/EntityInfo";
import * as TomgUtils from "./../Utils";
import { ColumnInfo } from "../models/ColumnInfo";
import { EntityInfo } from "../models/EntityInfo";
import * as TomgUtils from "../Utils";
export class MysqlDriver extends AbstractDriver {
readonly EngineName: string = "MySQL";
@ -45,7 +45,8 @@ export class MysqlDriver extends AbstractDriver {
})
.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp.COLUMN_NAME;
colInfo.tsName = resp.COLUMN_NAME;
colInfo.sqlName = resp.COLUMN_NAME;
colInfo.is_nullable = resp.IS_NULLABLE == "YES";
colInfo.is_generated = resp.IsIdentity == 1;
colInfo.is_unique = resp.column_key == "UNI";

View File

@ -1,7 +1,7 @@
import { AbstractDriver } from "./AbstractDriver";
import { ColumnInfo } from "./../models/ColumnInfo";
import { EntityInfo } from "./../models/EntityInfo";
import * as TomgUtils from "./../Utils";
import { ColumnInfo } from "../models/ColumnInfo";
import { EntityInfo } from "../models/EntityInfo";
import * as TomgUtils from "../Utils";
export class OracleDriver extends AbstractDriver {
Oracle: any;
@ -55,7 +55,8 @@ export class OracleDriver extends AbstractDriver {
})
.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp.COLUMN_NAME;
colInfo.tsName = resp.COLUMN_NAME;
colInfo.sqlName = resp.COLUMN_NAME;
colInfo.is_nullable = resp.NULLABLE == "Y";
colInfo.is_generated = resp.IDENTITY_COLUMN == "YES";
colInfo.default =

View File

@ -1,8 +1,8 @@
import { AbstractDriver } from "./AbstractDriver";
import * as PG from "pg";
import { ColumnInfo } from "./../models/ColumnInfo";
import { EntityInfo } from "./../models/EntityInfo";
import * as TomgUtils from "./../Utils";
import { ColumnInfo } from "../models/ColumnInfo";
import { EntityInfo } from "../models/EntityInfo";
import * as TomgUtils from "../Utils";
export class PostgresDriver extends AbstractDriver {
private Connection: PG.Client;
@ -54,7 +54,8 @@ export class PostgresDriver extends AbstractDriver {
})
.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp.column_name;
colInfo.tsName = resp.column_name;
colInfo.sqlName = resp.column_name;
colInfo.is_nullable = resp.is_nullable == "YES";
colInfo.is_generated = resp.isidentity == "YES";
colInfo.is_unique = resp.isunique == 1;

View File

@ -1,7 +1,7 @@
import { AbstractDriver } from "./AbstractDriver";
import { ColumnInfo } from "./../models/ColumnInfo";
import { EntityInfo } from "./../models/EntityInfo";
import * as TomgUtils from "./../Utils";
import { ColumnInfo } from "../models/ColumnInfo";
import { EntityInfo } from "../models/EntityInfo";
import * as TomgUtils from "../Utils";
export class SqliteDriver extends AbstractDriver {
sqlite = require("sqlite3").verbose();
@ -41,7 +41,8 @@ export class SqliteDriver extends AbstractDriver {
}>(`PRAGMA table_info('${ent.EntityName}');`);
response.forEach(resp => {
let colInfo: ColumnInfo = new ColumnInfo();
colInfo.name = resp.name;
colInfo.tsName = resp.name;
colInfo.sqlName = resp.name;
colInfo.is_nullable = resp.notnull == 0;
colInfo.isPrimary = resp.pk > 0;
colInfo.default = resp.dflt_value ? resp.dflt_value : null;
@ -231,7 +232,7 @@ export class SqliteDriver extends AbstractDriver {
indexInfo.isUnique
) {
ent.Columns.filter(
v => v.name == indexColumnInfo.name
v => v.tsName == indexColumnInfo.name
).map(v => (v.is_unique = true));
}
indexInfo.columns.push(indexColumnInfo);

View File

@ -1,4 +1,4 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
{{relationImports}}{{#each UniqueImports}}import {{curly true}}{{toEntityName this}}{{curly false}} from "./{{toFileName this}}";
{{/each}}
@ -20,15 +20,19 @@ import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, Man
scale:{{.}},{{/numericScale}}{{#isPrimary}}
primary:{{isPrimary}},{{/isPrimary}}{{#enumOptions}}
enum:[{{.}}],{{/enumOptions}}
name:"{{name}}"
name:"{{sqlName}}"
})
{{toPropertyName name}}:{{ts_type}};
{{toPropertyName tsName}}:{{ts_type}}{{#is_nullable}} | null{{/is_nullable}};
{{/relations}}{{#relations}}
@{{relationType}}(type=>{{toEntityName relatedTable}}, {{toPropertyName ../name}}=>{{toPropertyName ../name}}.{{#if isOwner}}{{toPropertyName ownerColumn}},{ {{#../isPrimary}}primary:true,{{/../isPrimary}}{{^../is_nullable}} nullable:false,{{/../is_nullable}}{{#actionOnDelete}}onDelete: '{{.}}',{{/actionOnDelete}}{{#actionOnUpdate}}onUpdate: '{{.}}'{{/actionOnUpdate}} }{{else}}{{toPropertyName relatedColumn}}{{#actionOnDelete}},{ onDelete: '{{.}}' }{{/actionOnDelete}}{{/if}}){{#isOwner}}
{{#if isManyToMany}}@JoinTable(){{else}}@JoinColumn({ name:'{{ ../name}}'}){{/if}}{{/isOwner}}
{{#if (or isOneToMany isManyToMany)}}{{toPropertyName ../name}}:{{toLazy (concat (toEntityName relatedTable) "[]")}};
{{else}}{{toPropertyName ../name}}:{{toLazy (toEntityName relatedTable)}};
{{/if}}{{/relations}}
@{{relationType}}(type=>{{toEntityName relatedTable}}, {{toEntityName relatedTable}}=>{{toEntityName relatedTable}}.{{#if isOwner}}{{toPropertyName ownerColumn}},{ {{#../isPrimary}}primary:true,{{/../isPrimary}}{{^../is_nullable}} nullable:false,{{/../is_nullable}}{{#actionOnDelete}}onDelete: '{{.}}',{{/actionOnDelete}}{{#actionOnUpdate}}onUpdate: '{{.}}'{{/actionOnUpdate}} }{{else}}{{toPropertyName relatedColumn}}{{#actionOnDelete}},{ onDelete: '{{.}}' }{{/actionOnDelete}}{{/if}}){{#isOwner}}
{{#if isManyToMany}}@JoinTable(){{else}}@JoinColumn({ name:'{{ ../sqlName}}'}){{/if}}{{/isOwner}}
{{#if (or isOneToMany isManyToMany)}}{{toPropertyName ../tsName}}:{{toLazy (concat (toEntityName relatedTable) "[]")}};
{{else}}{{toPropertyName ../tsName}}:{{toLazy (concat (toEntityName relatedTable) ' | null')}};
{{/if}}
{{#if relationIdField }}
@RelationId(({{../../EntityName}}: {{../../EntityName}}) => {{../../EntityName}}.{{toPropertyName ../tsName}})
{{toPropertyName ../tsName}}Id: {{#if isOneToOne}}{{toLazy ../ts_type}}{{else}}{{toLazy (concat ../ts_type "[]")}}{{/if}};{{/if}}{{/relations}}
{{/Columns}}
{{#if GenerateConstructor}}
constructor(init?: Partial<{{toEntityName EntityName}}>) {

View File

@ -9,6 +9,8 @@ import { Engine } from "./Engine";
import * as Yargs from "yargs";
import * as TomgUtils from "./Utils";
import path = require("path");
import { AbstractNamingStrategy } from "./AbstractNamingStrategy";
import { NamingStrategy } from "./NamingStrategy";
var argv = Yargs.usage(
"Usage: typeorm-model-generator -h <host> -d <database> -p [port] -u <user> -x [password] -e [engine]"
@ -84,6 +86,14 @@ var argv = Yargs.usage(
boolean: true,
default: false
})
.option("namingStrategy", {
describe: "Use custom naming strategy"
})
.option("relationIds", {
describe: "Generate RelationId fields",
boolean: true,
default: false
})
.option("generateConstructor", {
describe: "Generate constructor allowing partial initialization",
boolean: true,
@ -113,7 +123,7 @@ switch (argv.e) {
standardUser = "root";
break;
case "mariadb":
driver = new MysqlDriver();
driver = new MariaDbDriver();
standardPort = 3306;
standardUser = "root";
break;
@ -130,6 +140,13 @@ switch (argv.e) {
TomgUtils.LogError("Database engine not recognized.", false);
throw new Error("Database engine not recognized.");
}
let namingStrategy: AbstractNamingStrategy;
if (argv.namingStrategy && argv.namingStrategy != "") {
let req = require(argv.namingStrategy);
namingStrategy = new req.NamingStrategy();
} else {
namingStrategy = new NamingStrategy();
}
let engine = new Engine(driver, {
host: argv.h,
@ -146,7 +163,9 @@ let engine = new Engine(driver, {
convertCaseEntity: argv.ce,
convertCaseProperty: argv.cp,
lazy: argv.lazy,
constructor: argv.constructor
constructor: argv.generateConstructor,
relationIds: argv.relationIds,
namingStrategy: namingStrategy
});
console.log(TomgUtils.packageVersion());

View File

@ -1,7 +1,8 @@
import { RelationInfo } from "./RelationInfo";
export class ColumnInfo {
name: string = "";
tsName: string = "";
sqlName: string = "";
default: string | null = null;
is_nullable: boolean = false;
is_unique: boolean = false;
@ -24,7 +25,6 @@ export class ColumnInfo {
numericScale: number | null = null;
enumOptions: string | null = null;
relations: RelationInfo[];
constructor() {
this.relations = [];
}

View File

@ -7,6 +7,7 @@ export class RelationInfo {
ownerColumn: string;
actionOnDelete: "RESTRICT" | "CASCADE" | "SET NULL" | null;
actionOnUpdate: "RESTRICT" | "CASCADE" | "SET NULL" | null;
relationIdField: boolean = false;
get isOneToMany(): boolean {
return this.relationType == "OneToMany";
@ -14,4 +15,10 @@ export class RelationInfo {
get isManyToMany(): boolean {
return this.relationType == "ManyToMany";
}
get isOneToOne(): boolean {
return this.relationType == "OneToOne";
}
get isManyToOne(): boolean {
return this.relationType == "OneToOne";
}
}

View File

@ -1,11 +1,12 @@
import { expect } from "chai";
import { MssqlDriver } from './../../src/drivers/MssqlDriver'
import { MssqlDriver } from '../../src/drivers/MssqlDriver'
import * as Sinon from 'sinon'
import * as MSSQL from 'mssql'
import { EntityInfo } from './../../src/models/EntityInfo'
import { ColumnInfo } from './../../src/models/ColumnInfo'
import { RelationInfo } from './../../src/models/RelationInfo'
import { EntityInfo } from '../../src/models/EntityInfo'
import { ColumnInfo } from '../../src/models/ColumnInfo'
import { RelationInfo } from '../../src/models/RelationInfo'
import { Table, IColumnMetadata } from "mssql";
import { NamingStrategy } from "../../src/NamingStrategy";
class fakeResponse implements MSSQL.IResult<any> {
recordsets: MSSQL.IRecordSet<any>[];
@ -27,6 +28,7 @@ describe('MssqlDriver', function () {
beforeEach(() => {
driver = new MssqlDriver();
driver.namingStrategy = new NamingStrategy();
})
afterEach(() => {
@ -84,7 +86,8 @@ describe('MssqlDriver', function () {
is_nullable: true,
isPrimary: false,
is_generated: true,
name: 'name',
tsName: 'name',
sqlName: 'name',
numericPrecision: null,
numericScale: null,
width: null,
@ -92,7 +95,7 @@ describe('MssqlDriver', function () {
ts_type: 'number',
enumOptions: null,
is_unique:false,
relations: <RelationInfo[]>[]
relations: <RelationInfo[]>[],
})
let result = await driver.GetCoulmnsFromEntity(entities, 'schema');
expect(result).to.be.deep.equal(expected)

View File

@ -8,7 +8,7 @@ var chai = require('chai');
var chaiSubset = require('chai-subset');
import * as ts from "typescript";
import * as GTU from "../utils/GeneralTestUtils"
import { Engine } from "./../../src/Engine";
import { Engine } from "../../src/Engine";
chai.use(chaiSubset);

View File

@ -10,7 +10,7 @@ export class Category {
@Column()
name: string;
@ManyToMany(type => Post, post => post.Category)
Post: Promise<Post[]>;
@ManyToMany(type => Post, post => post.categorys)
posts: Promise<Post[]>;
}

View File

@ -21,10 +21,10 @@ export class Post {
})
author: Promise<Author | null>;
@ManyToMany(type => Category, category => category.Post, {
@ManyToMany(type => Category, category => category.posts, {
// cascade: true
})
@JoinTable()
Category: Promise<Category[]>;
categorys: Promise<Category[]>;
}

View File

@ -1,5 +1,5 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {PostDetails} from "./PostDetails";
import {PostDetail} from "./PostDetail";
import {PostCategory} from "./PostCategory";
import {PostAuthor} from "./PostAuthor";
import {PostInformation} from "./PostInformation";
@ -23,40 +23,40 @@ export class Post {
cascade: true
})
@JoinTable()
PostCategory: PostCategory[];
postCategorys: PostCategory[];
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
// relation it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostDetails, details => details.Post, {
@ManyToMany(type => PostDetail, details => details.posts, {
cascade: true
})
@JoinTable()
PostDetails: PostDetails[];
postDetails: PostDetail[];
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostImage, image => image.Post, {
@ManyToMany(type => PostImage, image => image.posts, {
cascade: true
})
@JoinTable()
PostImage: PostImage[];
postImages: PostImage[];
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToMany(type => PostMetadata, metadata => metadata.Post)
@ManyToMany(type => PostMetadata, metadata => metadata.posts)
@JoinTable()
PostMetadata: PostMetadata[];
postMetadatas: PostMetadata[];
// post has relation with details. full cascades here
@ManyToMany(type => PostInformation, information => information.Post, {
@ManyToMany(type => PostInformation, information => information.posts, {
cascade: true
})
@JoinTable()
PostInformation: PostInformation[];
postInformations: PostInformation[];
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@ManyToMany(type => PostAuthor, author => author.Post)
@ManyToMany(type => PostAuthor, author => author.posts)
@JoinTable()
PostAuthor: PostAuthor[];
postAuthors: PostAuthor[];
}

View File

@ -10,7 +10,7 @@ export class PostAuthor {
@Column()
name: string;
@ManyToMany(type => Post, post => post.PostAuthor)
Post: Post[];
@ManyToMany(type => Post, post => post.postAuthors)
posts: Post[];
}

View File

@ -1,8 +1,8 @@
import { PrimaryGeneratedColumn, Column, Entity, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable } from "typeorm";
import {Post} from "./Post";
@Entity("PostDetails")
export class PostDetails {
@Entity("PostDetail")
export class PostDetail {
@PrimaryGeneratedColumn()
id: number;
@ -16,7 +16,7 @@ export class PostDetails {
@Column()
metadata: string;
@ManyToMany(type => Post, post => post.PostDetails)
Post: Post[];
@ManyToMany(type => Post, post => post.postDetails)
posts: Post[];
}

View File

@ -10,7 +10,7 @@ export class PostImage {
@Column()
url: string;
@ManyToMany(type => Post, post => post.PostImage)
Post: Post[];
@ManyToMany(type => Post, post => post.postImages)
posts: Post[];
}

View File

@ -10,7 +10,7 @@ export class PostInformation {
@Column()
text: string;
@ManyToMany(type => Post, post => post.PostInformation)
Post: Post[];
@ManyToMany(type => Post, post => post.postInformations)
posts: Post[];
}

View File

@ -10,7 +10,7 @@ export class PostMetadata {
@Column()
description: string;
@ManyToMany(type => Post, post => post.PostMetadata)
Post: Post[];
@ManyToMany(type => Post, post => post.postMetadatas)
posts: Post[];
}

View File

@ -11,8 +11,8 @@ export class Post {
type: string;
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@ManyToMany(type => PostAuthor, author => author.Post)
@ManyToMany(type => PostAuthor, author => author.posts)
@JoinTable()
PostAuthor: PostAuthor[];
postAuthors: PostAuthor[];
}

View File

@ -15,7 +15,7 @@ export class PostAuthor {
@Column()
name: string;
@ManyToMany(type => Post, post => post.PostAuthor)
Post: Post[];
@ManyToMany(type => Post, post => post.postAuthors)
posts: Post[];
}

View File

@ -4,33 +4,33 @@ import {quests} from "./quests";
@Entity("feedextrainfo")
@Index("feedExtraInfo_FeedOwnerId_idx",["FeedOwnerId",])
@Index("feedExtraInfo_ReaderId_idx",["ReaderId",])
@Index("feedExtraInfo_QuestId_idx",["QuestId",])
@Index("feedExtraInfo_FeedOwnerId_idx",["feedOwnerId",])
@Index("feedExtraInfo_ReaderId_idx",["readerId",])
@Index("feedExtraInfo_QuestId_idx",["questId",])
export class feedextrainfo {
@OneToOne(type=>users, FeedOwnerId=>FeedOwnerId.feedextrainfo,{primary:true, nullable:false, })
@JoinColumn({ name:'FeedOwnerId'})
FeedOwnerId:users;
feedOwnerId:users;
@OneToOne(type=>quests, QuestId=>QuestId.feedextrainfo,{primary:true, nullable:false, })
@JoinColumn({ name:'QuestId'})
QuestId:quests;
questId:quests;
@OneToOne(type=>users, ReaderId=>ReaderId.feedextrainfo2,{primary:true, nullable:false, })
@JoinColumn({ name:'ReaderId'})
ReaderId:users;
readerId:users;
@Column("int",{
@Column("int",{
nullable:false,
name:"MostUpdatedFeedEntryIdUserRead"
})
MostUpdatedFeedEntryIdUserRead:number;
}

View File

@ -5,16 +5,16 @@ import {feedextrainfo} from "./feedextrainfo";
@Entity("quests")
export class quests {
@Column("int",{
@Column("int",{
nullable:false,
primary:true,
name:"QuestId"
})
QuestId:number;
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.QuestId)
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.questId)
feedextrainfo:feedextrainfo;
}

View File

@ -5,21 +5,21 @@ import {feedextrainfo} from "./feedextrainfo";
@Entity("users")
export class users {
@Column("int",{
@Column("int",{
nullable:false,
primary:true,
name:"UserId"
})
UserId:number;
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.FeedOwnerId)
@OneToOne(type=>feedextrainfo, feedextrainfo=>feedextrainfo.feedOwnerId)
feedextrainfo:feedextrainfo;
@OneToOne(type=>feedextrainfo, feedextrainfo2=>feedextrainfo2.ReaderId)
@OneToOne(type=>feedextrainfo, feedextrainfo2=>feedextrainfo2.readerId)
feedextrainfo2:feedextrainfo;
}

View File

@ -0,0 +1,24 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable} from "typeorm";
import { PostAuthor } from "./PostAuthor";
import { PostReader } from "./PostReader";
@Entity("Post")
export class Post {
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => PostAuthor, PostAuthor => PostAuthor.Id)
postAuthor: PostAuthor;
@OneToMany(type => PostReader, PostReader => PostReader.Id)
postReaders: PostReader[];
}

View File

@ -0,0 +1,23 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import { Post } from "./Post";
@Entity("PostAuthor")
export class PostAuthor {
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@OneToOne(type => Post, Post => Post.Id)
@JoinColumn()
post:Post;
@RelationId((postAuthor: PostAuthor) => postAuthor.post)
postId: number;
}

View File

@ -0,0 +1,21 @@
import {Index,Entity, PrimaryColumn, Column, OneToOne, OneToMany, ManyToOne, ManyToMany, JoinColumn, JoinTable, RelationId} from "typeorm";
import { Post } from "./Post";
@Entity("PostReader")
export class PostReader {
@Column("int",{
nullable:false,
primary:true,
name:"Id"
})
Id:number;
@ManyToOne(type => Post, Post => Post.Id)
@JoinColumn()
post:Post;
@RelationId((postReader: PostReader) => postReader.post)
postId: number[];
}

View File

@ -3,7 +3,7 @@ import "reflect-metadata";
import { createConnection, ConnectionOptions, Connection } from "typeorm";
import fs = require('fs-extra');
import path = require('path')
import { Engine } from "./../../src/Engine";
import { Engine } from "../../src/Engine";
import { expect } from "chai";
import * as Sinon from 'sinon'
import { EntityFileToJson } from "../utils/EntityFileToJson";
@ -78,6 +78,14 @@ describe("GitHub issues", async function () {
break;
}
switch (folder) {
case '65':
engine.Options.relationIds = true;
break;
default:
break;
}
await engine.createModelFromDatabase()
let filesGenPath = path.resolve(resultsPath, 'entities')

View File

@ -2,7 +2,7 @@ require('dotenv').config()
import "reflect-metadata";
import fs = require('fs-extra');
import path = require('path')
import { Engine } from "./../../src/Engine";
import { Engine } from "../../src/Engine";
import { expect } from "chai";
import { EntityFileToJson } from "../utils/EntityFileToJson";
var chai = require('chai');

View File

@ -1,7 +1,7 @@
import * as ts from "typescript";
import { AbstractDriver } from "../../src/drivers/AbstractDriver";
import { MssqlDriver } from "../../src/drivers/MssqlDriver";
import { PostgresDriver } from "./../../src/drivers/PostgresDriver";
import { PostgresDriver } from "../../src/drivers/PostgresDriver";
import { MysqlDriver } from "../../src/drivers/MysqlDriver";
import { MariaDbDriver } from "../../src/drivers/MariaDbDriver";
import { OracleDriver } from "../../src/drivers/OracleDriver";
@ -10,6 +10,8 @@ import { Engine } from "../../src/Engine";
import { createConnection, ConnectionOptions } from "typeorm";
import * as yn from "yn"
import path = require('path')
import { AbstractNamingStrategy } from "../../src/AbstractNamingStrategy";
import { NamingStrategy } from "../../src/NamingStrategy";
export async function createMSSQLModels(filesOrgPath: string, resultsPath: string): Promise<Engine> {
@ -45,6 +47,7 @@ export async function createMSSQLModels(filesOrgPath: string, resultsPath: strin
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new MssqlDriver();
let engine = new Engine(
@ -63,7 +66,9 @@ export async function createMSSQLModels(filesOrgPath: string, resultsPath: strin
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor: false,
namingStrategy: namingStrategy,
relationIds:false
});
conn = await createConnection(connOpt)
@ -110,6 +115,7 @@ export async function createPostgresModels(filesOrgPath: string, resultsPath: st
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new PostgresDriver();
let engine = new Engine(
@ -128,7 +134,9 @@ export async function createPostgresModels(filesOrgPath: string, resultsPath: st
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor:false,
namingStrategy: namingStrategy,
relationIds: false
});
conn = await createConnection(connOpt)
@ -167,6 +175,7 @@ export async function createSQLiteModels(filesOrgPath: string, resultsPath: stri
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new SqliteDriver();
let engine = new Engine(
@ -185,7 +194,9 @@ export async function createSQLiteModels(filesOrgPath: string, resultsPath: stri
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor:false,
namingStrategy: namingStrategy,
relationIds: false
});
conn = await createConnection(connOpt)
@ -222,6 +233,7 @@ export async function createMysqlModels(filesOrgPath: string, resultsPath: strin
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new MysqlDriver();
let engine = new Engine(
@ -240,7 +252,9 @@ export async function createMysqlModels(filesOrgPath: string, resultsPath: strin
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor:false,
namingStrategy: namingStrategy,
relationIds: false
});
return engine;
@ -270,6 +284,7 @@ export async function createMariaDBModels(filesOrgPath: string, resultsPath: str
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new MariaDbDriver();
let engine = new Engine(
@ -288,7 +303,9 @@ export async function createMariaDBModels(filesOrgPath: string, resultsPath: str
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor:false,
namingStrategy: namingStrategy,
relationIds: false
});
@ -321,6 +338,7 @@ export async function createOracleDBModels(filesOrgPath: string, resultsPath: st
if (conn.isConnected)
await conn.close()
let namingStrategy: AbstractNamingStrategy = new NamingStrategy();
driver = new OracleDriver();
let engine = new Engine(
@ -339,7 +357,9 @@ export async function createOracleDBModels(filesOrgPath: string, resultsPath: st
convertCaseFile: 'none',
convertCaseProperty: 'none',
lazy: false,
constructor:false
constructor:false,
namingStrategy: namingStrategy,
relationIds: false
});
return engine;