Skip to content

Commit

Permalink
add way to load entitles as stand alone (#3)
Browse files Browse the repository at this point in the history
* inital: implmemnt basic logic to load typeorm models to typescript

* ts: add estlint to script

* .github: add ci workflow of lint

* ts: install sqlite and mssql drivers

* create integration test dir

* rename

* add preitier

* add way to load as standalone

* .github: add test for the standalone in the ci

* fix comment
  • Loading branch information
ronenlu authored Oct 2, 2023
1 parent 33e2b5e commit 16a85fb
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 68 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,20 @@ jobs:
- name: Run Test as Standalone
working-directory: ./ts/testdata
run: |
atlas migrate diff --env typeorm --var dialect=${{ matrix.dialect }}
atlas migrate diff --env typeorm -c "file://atlas-standalone.hcl" --var dialect=${{ matrix.dialect }}
- name: Verify migrations generated
run: |
status=$(git status --porcelain)
if [ -n "$status" ]; then
echo "you need to run 'atlas migrate diff --env typeorm' and commit the changes"
echo "$status"
git --no-pager diff
exit 1
fi
- name: Run Test as ts Script
working-directory: ./ts/testdata
run: |
atlas migrate diff --env typeorm -c "file://atlas-script.hcl" --var dialect=${{ matrix.dialect }}
- name: Verify migrations generated
run: |
status=$(git status --porcelain)
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion ts/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"name": "@ariga/ts-atlas-provider-sequelize",
"name": "@ariga/ts-atlas-provider-typeorm",
"description": "Load Typeorm models into an Atlas project.",
"version": "0.0.1",
"type": "commonjs",
"bin": "src/index.ts",
"devDependencies": {
"@types/node": "^16.11.10",
"@types/yargs": "^17.0.26",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"eslint": "^8.50.0",
Expand Down
105 changes: 42 additions & 63 deletions ts/src/index.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,66 +1,45 @@
import { DataSource, EntitySchema, Table, TableForeignKey } from "typeorm";
import { ConnectionMetadataBuilder } from "typeorm/connection/ConnectionMetadataBuilder";
import { EntityMetadataValidator } from "typeorm/metadata-builder/EntityMetadataValidator";
import { View } from "typeorm/schema-builder/view/View";
#! /usr/bin/env ts-node-script

export type Dialect = "mysql" | "postgres" | "mariadb" | "sqlite" | "mssql";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { Dialect, loadEntities } from "./load";
import * as fs from "fs";

// Load typeorm entities and return SQL statements describing the schema of the entities.
export async function loadEntities(
dialect: Dialect,
// eslint-disable-next-line @typescript-eslint/ban-types
entities: (Function | EntitySchema | string)[],
) {
const mockDB = new DataSource({
type: dialect,
database: ":memory:",
});
const driver = mockDB.driver;
const metadataBuilder = new ConnectionMetadataBuilder(mockDB);
const entityMetadataValidator = new EntityMetadataValidator();
const queryRunner = mockDB.createQueryRunner();
queryRunner.enableSqlMemory();

const entityMetadatas = await metadataBuilder.buildEntityMetadatas(entities);
// validate all created entity metadatas to make sure user created entities are valid and correct
entityMetadataValidator.validateMany(
entityMetadatas.filter((metadata) => metadata.tableType !== "view"),
driver,
);

// create tables statements
for (const metadata of entityMetadatas) {
if (metadata.tableType === "view") {
continue;
}
const table = Table.create(metadata, driver);
await queryRunner.createTable(table);
}

// create foreign keys statements,
// executed after all tables created since foreign keys can reference tables that were created afterwards.
for (const metadata of entityMetadatas) {
const table = Table.create(metadata, driver);
const foreignKeys = metadata.foreignKeys.map((foreignKeyMetadata) =>
TableForeignKey.create(foreignKeyMetadata, driver),
);
await queryRunner.createForeignKeys(table, foreignKeys);
}

// create views
for (const metadata of entityMetadatas) {
if (metadata.tableType !== "view") {
continue;
const y = yargs(hideBin(process.argv))
.usage(
"npx @ariga/ts-atlas-provider-typeorm load --path ./models --dialect mysql",
)
.alias("h", "help");
y.command(
"load",
"load sql state of typeorm entities",
{
path: {
type: "string",
demandOption: true,
describe: "Path to models folder",
},
dialect: {
type: "string",
choices: ["mysql", "postgres", "sqlite", "mariadb", "mssql"],
demandOption: true,
describe: "Dialect of database",
},
},
async function (argv) {
try {
const path = argv.path;
if (!fs.existsSync(path)) {
throw new Error(`path ${path} does not exist`);
}
const sql = await loadEntities(argv.dialect as Dialect, [path + "/*.ts"]);
console.log(sql);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
} else {
console.error(e);
}
}
const view = View.create(metadata, driver);
await queryRunner.createView(view, false);
}

let sql = "";
const memorySql = queryRunner.getMemorySql();
memorySql.upQueries.forEach((query) => {
sql += query.query + ";\n";
});
queryRunner.clearSqlMemory();
return sql;
}
},
).parse();
62 changes: 62 additions & 0 deletions ts/src/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DataSource, EntitySchema, Table, TableForeignKey } from "typeorm";
import { ConnectionMetadataBuilder } from "typeorm/connection/ConnectionMetadataBuilder";
import { EntityMetadataValidator } from "typeorm/metadata-builder/EntityMetadataValidator";
import { View } from "typeorm/schema-builder/view/View";

export type Dialect = "mysql" | "postgres" | "mariadb" | "sqlite" | "mssql";

// Load typeorm entities and return SQL statements describing the schema of the entities.
export async function loadEntities(
dialect: Dialect,
// eslint-disable-next-line @typescript-eslint/ban-types
entities: (Function | EntitySchema | string)[],
) {
const mockDB = new DataSource({
type: dialect,
database: ":memory:",
});
const driver = mockDB.driver;
const metadataBuilder = new ConnectionMetadataBuilder(mockDB);
const entityMetadataValidator = new EntityMetadataValidator();
const queryRunner = mockDB.createQueryRunner();
queryRunner.enableSqlMemory();

const entityMetadatas = await metadataBuilder.buildEntityMetadatas(entities);
// validate all created entity metadatas to make sure user created entities are valid and correct
entityMetadataValidator.validateMany(
entityMetadatas.filter((metadata) => metadata.tableType !== "view"),
driver,
);

// create tables statements
for (const metadata of entityMetadatas) {
if (metadata.tableType === "view") {
continue;
}
const table = Table.create(metadata, driver);
await queryRunner.createTable(table);
}

// creating foreign keys statements are executed after all tables created since foreign keys can reference tables that were created afterwards.
for (const metadata of entityMetadatas) {
const table = Table.create(metadata, driver);
const foreignKeys = metadata.foreignKeys.map((foreignKeyMetadata) =>
TableForeignKey.create(foreignKeyMetadata, driver),
);
await queryRunner.createForeignKeys(table, foreignKeys);
}

// create views
for (const metadata of entityMetadatas) {
if (metadata.tableType !== "view") {
continue;
}
const view = View.create(metadata, driver);
await queryRunner.createView(view, false);
}

const memorySql = queryRunner.getMemorySql();
const queries = memorySql.upQueries.map((query) => query.query);
queryRunner.clearSqlMemory();
return queries.join(";\n");
}
File renamed without changes.
34 changes: 34 additions & 0 deletions ts/testdata/atlas-standalone.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
variable "dialect" {
type = string
}

locals {
dev_url = {
mysql = "docker://mysql/8/dev"
postgres = "docker://postgres/15"
sqlite = "sqlite://file::memory:?cache=shared"
}[var.dialect]
}

data "external_schema" "typeorm" {
program = [
"npx",
"../",
"load",
"--path", "./entities",
"--dialect", var.dialect,
]
}

env "typeorm" {
src = data.external_schema.typeorm.url
dev = local.dev_url
migration {
dir = "file://migrations/${var.dialect}"
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
2 changes: 1 addition & 1 deletion ts/testdata/load-entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#! /usr/bin/env ts-node-script

import { Dialect, loadEntities } from "../src";
import { Dialect, loadEntities } from "../src/load";
import { User } from "./entities/User";
import { Blog } from "./entities/Blog";

Expand Down

0 comments on commit 16a85fb

Please sign in to comment.