From f06732be8fa752b4479e39cf6f61ac5288ba197e Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Thu, 14 Sep 2023 15:51:35 +0300 Subject: [PATCH 1/7] ts: add way to load sequelize models programmatically as ts script --- ts/index.ts | 1 + ts/src/sequelize_schema.ts | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 ts/index.ts create mode 100644 ts/src/sequelize_schema.ts diff --git a/ts/index.ts b/ts/index.ts new file mode 100644 index 0000000..9e8fc78 --- /dev/null +++ b/ts/index.ts @@ -0,0 +1 @@ +export * from "./src/sequelize_schema"; diff --git a/ts/src/sequelize_schema.ts b/ts/src/sequelize_schema.ts new file mode 100644 index 0000000..4484bcd --- /dev/null +++ b/ts/src/sequelize_schema.ts @@ -0,0 +1,44 @@ +import { ModelCtor } from "sequelize-typescript/dist/model/model/model"; +import { Sequelize, SequelizeOptions } from "sequelize-typescript"; + +const validDialects = ["mysql", "postgres", "sqlite", "mariadb", "mssql"]; + +// load sql state of sequelize models +export const loadSequelizeModels = (dialect: string, models: ModelCtor[]) => { + if (!validDialects.includes(dialect)) { + throw new Error(`Invalid dialect ${dialect}`); + } + const sequelize = new Sequelize({ + dialect: dialect, + models: models, + } as SequelizeOptions); + const orderedModels = sequelize.modelManager + .getModelsTopoSortedByForeignKey() + ?.reverse(); + if (!orderedModels) { + throw new Error("no models found"); + } + let sql = ""; + for (const model of models) { + const def = sequelize.modelManager.getModel(model.name); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const attr = sequelize.getQueryInterface().queryGenerator.attributesToSQL( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + def.getAttributes(), + Object.assign({}, def.options), + ); + sql += + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + sequelize + .getQueryInterface() + .queryGenerator.createTableQuery( + def.tableName, + attr, + Object.assign({}, def.options), + ) + "\n"; + } + return sql; +}; From 5067a6610d5aac723853e4555b92fc514ee03760 Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Thu, 14 Sep 2023 15:57:22 +0300 Subject: [PATCH 2/7] ts: add hcl for testing programmaticly --- ts/src/sequelize_schema.ts | 2 +- ts/testdata/atlas-script.hcl | 32 ++++++++++++++++++++++++++++++++ ts/testdata/load-models.ts | 12 ++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 ts/testdata/atlas-script.hcl create mode 100644 ts/testdata/load-models.ts diff --git a/ts/src/sequelize_schema.ts b/ts/src/sequelize_schema.ts index 4484bcd..2181b31 100644 --- a/ts/src/sequelize_schema.ts +++ b/ts/src/sequelize_schema.ts @@ -19,7 +19,7 @@ export const loadSequelizeModels = (dialect: string, models: ModelCtor[]) => { throw new Error("no models found"); } let sql = ""; - for (const model of models) { + for (const model of orderedModels) { const def = sequelize.modelManager.getModel(model.name); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/ts/testdata/atlas-script.hcl b/ts/testdata/atlas-script.hcl new file mode 100644 index 0000000..f42afbd --- /dev/null +++ b/ts/testdata/atlas-script.hcl @@ -0,0 +1,32 @@ +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" "sequelize" { + program = [ + "ts-node", + "load-models.ts", + var.dialect, + ] +} + +env "sequelize" { + src = data.external_schema.sequelize.url + dev = local.dev_url + migration { + dir = "file://migrations/${var.dialect}" + } + format { + migrate { + diff = "{{ sql . \" \" }}" + } + } +} diff --git a/ts/testdata/load-models.ts b/ts/testdata/load-models.ts new file mode 100644 index 0000000..b713aeb --- /dev/null +++ b/ts/testdata/load-models.ts @@ -0,0 +1,12 @@ +#! /usr/bin/env ts-node-script + +import Phone from "./models/Phone"; +import Email from "./models/Email"; +import Contact from "./models/Contact"; +import { loadSequelizeModels } from "../src/sequelize_schema"; + +// parse the second argument as the dialect +const dialect = process.argv[2]; + +// load the models +console.log(loadSequelizeModels(dialect, [Phone, Email, Contact])); From 5182f0b4f4e37f6cb5113883df441a3f66d604fa Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Thu, 14 Sep 2023 16:50:17 +0300 Subject: [PATCH 3/7] update workflow --- .github/workflows/ci.yaml | 2 -- ts/testdata/atlas-script.hcl | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0aade32..251e708 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -67,8 +67,6 @@ jobs: fi - name: Run Test as ${{ matrix.language }} Script working-directory: ./${{ matrix.language }}/testdata - # TODO: remove the check if file exists once we support loading models from ts script - if: ${{ hashFiles('atlas-script.hcl') != '' }} run: | atlas migrate diff --env sequelize -c "file://atlas-script.hcl" --var dialect=${{ matrix.dialect }} - name: Verify migrations generated diff --git a/ts/testdata/atlas-script.hcl b/ts/testdata/atlas-script.hcl index f42afbd..47d5550 100644 --- a/ts/testdata/atlas-script.hcl +++ b/ts/testdata/atlas-script.hcl @@ -12,6 +12,7 @@ locals { data "external_schema" "sequelize" { program = [ + "npx", "ts-node", "load-models.ts", var.dialect, From 9affaa8c0f47d3770c30db68b19f982dfa2596bf Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Mon, 18 Sep 2023 10:52:32 +0300 Subject: [PATCH 4/7] add on delete constraint to schema --- ts/src/sequelize_schema.ts | 2 ++ ts/testdata/models/Email.ts | 4 +++- ts/testdata/models/Phone.ts | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ts/src/sequelize_schema.ts b/ts/src/sequelize_schema.ts index 2181b31..780188f 100644 --- a/ts/src/sequelize_schema.ts +++ b/ts/src/sequelize_schema.ts @@ -34,6 +34,8 @@ export const loadSequelizeModels = (dialect: string, models: ModelCtor[]) => { // @ts-ignore sequelize .getQueryInterface() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore .queryGenerator.createTableQuery( def.tableName, attr, diff --git a/ts/testdata/models/Email.ts b/ts/testdata/models/Email.ts index c5d76f9..47f17b3 100644 --- a/ts/testdata/models/Email.ts +++ b/ts/testdata/models/Email.ts @@ -54,7 +54,9 @@ class Email extends Model { @Column(DataType.INTEGER) contact_id!: number; - @BelongsTo(() => Contact) + @BelongsTo(() => Contact, { + onDelete: "CASCADE", + }) contact!: Contact; @CreatedAt diff --git a/ts/testdata/models/Phone.ts b/ts/testdata/models/Phone.ts index 22558c3..ed8845c 100644 --- a/ts/testdata/models/Phone.ts +++ b/ts/testdata/models/Phone.ts @@ -39,7 +39,9 @@ class Phone extends Model { @Column(DataType.INTEGER) contact_id!: number; - @BelongsTo(() => Contact) + @BelongsTo(() => Contact, { + onDelete: "CASCADE", + }) contact!: Contact; @CreatedAt From 61d5142ab8bb2036da2fb592dafca98295976efc Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Mon, 18 Sep 2023 13:31:18 +0300 Subject: [PATCH 5/7] build the loader --- ts/index.ts | 1 - ts/src/sequelize_schema.d.ts | 2 ++ ts/src/sequelize_schema.js | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) delete mode 100644 ts/index.ts create mode 100644 ts/src/sequelize_schema.d.ts create mode 100644 ts/src/sequelize_schema.js diff --git a/ts/index.ts b/ts/index.ts deleted file mode 100644 index 9e8fc78..0000000 --- a/ts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./src/sequelize_schema"; diff --git a/ts/src/sequelize_schema.d.ts b/ts/src/sequelize_schema.d.ts new file mode 100644 index 0000000..3936ff0 --- /dev/null +++ b/ts/src/sequelize_schema.d.ts @@ -0,0 +1,2 @@ +import { ModelCtor } from "sequelize-typescript/dist/model/model/model"; +export declare const loadSequelizeModels: (dialect: string, models: ModelCtor[]) => string; diff --git a/ts/src/sequelize_schema.js b/ts/src/sequelize_schema.js new file mode 100644 index 0000000..c933cad --- /dev/null +++ b/ts/src/sequelize_schema.js @@ -0,0 +1,42 @@ +"use strict"; +exports.__esModule = true; +exports.loadSequelizeModels = void 0; +var sequelize_typescript_1 = require("sequelize-typescript"); +var validDialects = ["mysql", "postgres", "sqlite", "mariadb", "mssql"]; +// load sql state of sequelize models +var loadSequelizeModels = function (dialect, models) { + var _a; + if (!validDialects.includes(dialect)) { + throw new Error("Invalid dialect ".concat(dialect)); + } + var sequelize = new sequelize_typescript_1.Sequelize({ + dialect: dialect, + models: models + }); + var orderedModels = (_a = sequelize.modelManager + .getModelsTopoSortedByForeignKey()) === null || _a === void 0 ? void 0 : _a.reverse(); + if (!orderedModels) { + throw new Error("no models found"); + } + var sql = ""; + for (var _i = 0, orderedModels_1 = orderedModels; _i < orderedModels_1.length; _i++) { + var model = orderedModels_1[_i]; + var def = sequelize.modelManager.getModel(model.name); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + var attr = sequelize.getQueryInterface().queryGenerator.attributesToSQL( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + def.getAttributes(), Object.assign({}, def.options)); + sql += + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + sequelize + .getQueryInterface() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + .queryGenerator.createTableQuery(def.tableName, attr, Object.assign({}, def.options)) + "\n"; + } + return sql; +}; +exports.loadSequelizeModels = loadSequelizeModels; From 2c3550a7ac58dd4c61fc57ef1aeec95ee50170c0 Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Mon, 18 Sep 2023 13:36:47 +0300 Subject: [PATCH 6/7] add building job to ci of typescript --- .github/workflows/ci.yaml | 28 ++++++++++++++++++++++++++++ ts/package.json | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 251e708..1570b21 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,6 +31,34 @@ jobs: - name: Lint check run: npm run fmt-check working-directory: ./${{ matrix.language }} + build-ts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18.16.0 + - name: Cache node modules + uses: actions/cache@v3 + with: + path: ./ts/node_modules + key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }} + restore-keys: node_modules- + - name: Install + run: npm install + working-directory: ./ts + - name: Build Typescript Files + run: npm run build + working-directory: ./ts + - name: Verify migrations generated + run: | + status=$(git status --porcelain) + if [ -n "$status" ]; then + echo "you need to run 'npm run build' and commit the changes" + echo "$status" + git --no-pager diff + exit 1 + fi integration-tests: strategy: matrix: diff --git a/ts/package.json b/ts/package.json index 88ce930..780e868 100644 --- a/ts/package.json +++ b/ts/package.json @@ -8,7 +8,8 @@ "license": "ISC", "scripts": { "fmt": "eslint src/**/*.ts --fix && prettier --write .", - "fmt-check": "npx eslint ./src --max-warnings=0 && prettier --check ." + "fmt-check": "npx eslint ./src --max-warnings=0 && prettier --check .", + "build": "tsc -d src/sequelize_schema.ts" }, "dependencies": { "@types/node": "^20.5.7", From 266623afd381552793adc9bf0521ae1d67bf80fd Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Mon, 18 Sep 2023 13:41:16 +0300 Subject: [PATCH 7/7] add build files to .prettierignore --- js/testdata/load-models.js | 1 - ts/.prettierignore | 2 ++ ts/package.json | 2 +- ts/src/sequelize_schema.d.ts | 2 +- ts/src/sequelize_schema.js | 6 +++--- ts/src/sequelize_schema.ts | 2 +- ts/testdata/load-models.ts | 5 ++--- 7 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 ts/.prettierignore diff --git a/js/testdata/load-models.js b/js/testdata/load-models.js index 407c3f4..5db0d56 100644 --- a/js/testdata/load-models.js +++ b/js/testdata/load-models.js @@ -8,5 +8,4 @@ const loadModels = require("../index"); // parse the second argument as the dialect const dialect = process.argv[2]; -// load the models console.log(loadModels(dialect, ingredient, recipe, recipeIngredient)); diff --git a/ts/.prettierignore b/ts/.prettierignore new file mode 100644 index 0000000..4f1a04f --- /dev/null +++ b/ts/.prettierignore @@ -0,0 +1,2 @@ +src/sequelize_schema.d.ts +src/sequelize_schema.js diff --git a/ts/package.json b/ts/package.json index 780e868..9606a5b 100644 --- a/ts/package.json +++ b/ts/package.json @@ -8,7 +8,7 @@ "license": "ISC", "scripts": { "fmt": "eslint src/**/*.ts --fix && prettier --write .", - "fmt-check": "npx eslint ./src --max-warnings=0 && prettier --check .", + "fmt-check": "eslint src/**/*.ts --max-warnings=0 && prettier --check .", "build": "tsc -d src/sequelize_schema.ts" }, "dependencies": { diff --git a/ts/src/sequelize_schema.d.ts b/ts/src/sequelize_schema.d.ts index 3936ff0..0e842aa 100644 --- a/ts/src/sequelize_schema.d.ts +++ b/ts/src/sequelize_schema.d.ts @@ -1,2 +1,2 @@ import { ModelCtor } from "sequelize-typescript/dist/model/model/model"; -export declare const loadSequelizeModels: (dialect: string, models: ModelCtor[]) => string; +export declare const loadModels: (dialect: string, models: ModelCtor[]) => string; diff --git a/ts/src/sequelize_schema.js b/ts/src/sequelize_schema.js index c933cad..dbbc76e 100644 --- a/ts/src/sequelize_schema.js +++ b/ts/src/sequelize_schema.js @@ -1,10 +1,10 @@ "use strict"; exports.__esModule = true; -exports.loadSequelizeModels = void 0; +exports.loadModels = void 0; var sequelize_typescript_1 = require("sequelize-typescript"); var validDialects = ["mysql", "postgres", "sqlite", "mariadb", "mssql"]; // load sql state of sequelize models -var loadSequelizeModels = function (dialect, models) { +var loadModels = function (dialect, models) { var _a; if (!validDialects.includes(dialect)) { throw new Error("Invalid dialect ".concat(dialect)); @@ -39,4 +39,4 @@ var loadSequelizeModels = function (dialect, models) { } return sql; }; -exports.loadSequelizeModels = loadSequelizeModels; +exports.loadModels = loadModels; diff --git a/ts/src/sequelize_schema.ts b/ts/src/sequelize_schema.ts index 780188f..2ceaf8c 100644 --- a/ts/src/sequelize_schema.ts +++ b/ts/src/sequelize_schema.ts @@ -4,7 +4,7 @@ import { Sequelize, SequelizeOptions } from "sequelize-typescript"; const validDialects = ["mysql", "postgres", "sqlite", "mariadb", "mssql"]; // load sql state of sequelize models -export const loadSequelizeModels = (dialect: string, models: ModelCtor[]) => { +export const loadModels = (dialect: string, models: ModelCtor[]) => { if (!validDialects.includes(dialect)) { throw new Error(`Invalid dialect ${dialect}`); } diff --git a/ts/testdata/load-models.ts b/ts/testdata/load-models.ts index b713aeb..a84757e 100644 --- a/ts/testdata/load-models.ts +++ b/ts/testdata/load-models.ts @@ -3,10 +3,9 @@ import Phone from "./models/Phone"; import Email from "./models/Email"; import Contact from "./models/Contact"; -import { loadSequelizeModels } from "../src/sequelize_schema"; +import { loadModels } from "../src/sequelize_schema"; // parse the second argument as the dialect const dialect = process.argv[2]; -// load the models -console.log(loadSequelizeModels(dialect, [Phone, Email, Contact])); +console.log(loadModels(dialect, [Phone, Email, Contact]));