Skip to content

Commit

Permalink
Merge pull request #7 from ariga/sequelize_schema
Browse files Browse the repository at this point in the history
js: add way to load sequelize models programmatically as js script
  • Loading branch information
ronenlu authored Sep 17, 2023
2 parents 243e2ff + 6d6c0e8 commit d40d1cb
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 38 deletions.
22 changes: 19 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Lint check
run: npm run fmt-check
working-directory: ./${{ matrix.language }}
integration-test:
integration-tests:
strategy:
matrix:
dialect: [ mysql, postgres, sqlite ]
Expand All @@ -52,9 +52,25 @@ jobs:
run: npm install
working-directory: ./${{ matrix.language }}
- uses: ariga/setup-atlas@master
- working-directory: ./${{ matrix.language }}/testdata
- name: Run Test as Standalone
working-directory: ./${{ matrix.language }}/testdata
run: |
atlas migrate diff --env sequelize --var dialect=${{ matrix.dialect }}
atlas migrate diff --env sequelize -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 sequelize' and commit the changes"
echo "$status"
git --no-pager diff
exit 1
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
run: |
status=$(git status --porcelain)
Expand Down
3 changes: 3 additions & 0 deletions js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const loadSequelizeModels = require("./src/sequelize_schema");

module.exports = loadSequelizeModels;
40 changes: 5 additions & 35 deletions js/src/index.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,29 @@
#!/usr/bin/env node

const Sequelize = require("sequelize");
const DataTypes = require("sequelize/lib/data-types");
const yargs = require("yargs");
const fs = require("fs");
const { hideBin } = require("yargs/helpers");
const process = require("process");
const { resolve } = require("path");
const loadSequelizeModels = require("./sequelize_schema");

// get sql state of sequelize models
const loadSQL = (relativePath, driver) => {
let sequelize = new Sequelize({
dialect: driver,
});
if (!fs.existsSync(relativePath)) {
throw new Error("path does not exist");
}
const absolutePath = resolve(relativePath);
// get all models from files in models folder
const files = fs.readdirSync(absolutePath);
const db = {};
const models = [];
for (const file of files) {
if (file.match(/\.js$/) !== null && file !== "index.js") {
const name = file.replace(".js", "");
const m = require(absolutePath + "/" + name)(sequelize, DataTypes);
if (m?.name) {
db[m.name] = m;
}
}
}
// create associations between models
for (const modelName of Object.keys(db)) {
if (db[modelName]?.associate) {
db[modelName].associate(db);
const m = require(absolutePath + "/" + name);
models.push(m);
}
}

const models = sequelize.modelManager
.getModelsTopoSortedByForeignKey()
.reverse();

let sql = "";
for (const model of models) {
const def = sequelize.modelManager.getModel(model.name);
const attr = sequelize
.getQueryInterface()
.queryGenerator.attributesToSQL(def.getAttributes(), { ...def.options });
sql +=
sequelize
.getQueryInterface()
.queryGenerator.createTableQuery(def.tableName, attr, {
...def.options,
}) + "\n";
}
return sql;
return loadSequelizeModels(driver, ...models);
};

yargs(hideBin(process.argv))
Expand Down
47 changes: 47 additions & 0 deletions js/src/sequelize_schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const Sequelize = require("sequelize");
const DataTypes = require("sequelize/lib/data-types");

const validDialects = ["mysql", "postgres", "sqlite", "mariadb", "mssql"];

// gets dialect and models that are functions of the form (sequelize, DataTypes) => Model
// returns DDL string describing the models.
const loadSequelizeModels = (dialect, ...models) => {
if (!validDialects.includes(dialect)) {
throw new Error("invalid dialect: " + dialect);
}
let sequelize = new Sequelize({
dialect: dialect,
});
const db = {};
for (const model of models) {
const m = model(sequelize, DataTypes);
if (m?.name) {
db[m.name] = m;
}
}
// create associations between models
for (const modelName of Object.keys(db)) {
if (db[modelName]?.associate) {
db[modelName].associate(db);
}
}
const modelsOrdered = sequelize.modelManager
.getModelsTopoSortedByForeignKey()
.reverse();
let sql = "";
for (const model of modelsOrdered) {
const def = sequelize.modelManager.getModel(model.name);
const attr = sequelize
.getQueryInterface()
.queryGenerator.attributesToSQL(def.getAttributes(), { ...def.options });
sql +=
sequelize
.getQueryInterface()
.queryGenerator.createTableQuery(def.tableName, attr, {
...def.options,
}) + "\n";
}
return sql;
};

module.exports = loadSequelizeModels;
32 changes: 32 additions & 0 deletions js/testdata/atlas-script.hcl
Original file line number Diff line number Diff line change
@@ -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 = [
"node",
"load-models.js",
var.dialect,
]
}

env "sequelize" {
src = data.external_schema.sequelize.url
dev = local.dev_url
migration {
dir = "file://migrations/${var.dialect}"
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
File renamed without changes.
12 changes: 12 additions & 0 deletions js/testdata/load-models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node

const ingredient = require("./models/ingredient");
const recipe = require("./models/recipe");
const recipeIngredient = require("./models/recipe-ingredient");
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));
File renamed without changes.

0 comments on commit d40d1cb

Please sign in to comment.