From 8a3c1f43783bff34759d3bceb90dd3c01d7fd3b2 Mon Sep 17 00:00:00 2001 From: Jeff Tian Date: Thu, 28 Nov 2019 15:15:47 +0800 Subject: [PATCH 1/3] feat: add Sequelize to config's declaration and test it works with sequelize-typescript --- .gitignore | 1 + README.md | 87 ++++++++++++++++++- index.d.ts | 9 +- lib/loader.js | 2 +- package.json | 6 +- test.sh | 28 ++++++ .../apps/typescript/app/controller/home.ts | 10 +++ .../apps/typescript/app/model/monkey.ts | 27 ++++++ .../apps/typescript/app/model/user.ts | 27 ++++++ test/fixtures/apps/typescript/app/router.ts | 7 ++ .../apps/typescript/config/config.default.ts | 41 +++++++++ test/fixtures/apps/typescript/package.json | 3 + test/fixtures/apps/typescript/tsconfig.json | 17 ++++ .../apps/typescript/typings/index.d.ts | 16 ++++ test/ts.test.js | 36 ++++++++ 15 files changed, 312 insertions(+), 5 deletions(-) create mode 100644 test.sh create mode 100644 test/fixtures/apps/typescript/app/controller/home.ts create mode 100644 test/fixtures/apps/typescript/app/model/monkey.ts create mode 100644 test/fixtures/apps/typescript/app/model/user.ts create mode 100644 test/fixtures/apps/typescript/app/router.ts create mode 100644 test/fixtures/apps/typescript/config/config.default.ts create mode 100644 test/fixtures/apps/typescript/package.json create mode 100644 test/fixtures/apps/typescript/tsconfig.json create mode 100644 test/fixtures/apps/typescript/typings/index.d.ts diff --git a/.gitignore b/.gitignore index 794e505..c5cdb30 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ run test/fixtures/apps/model-app/run/ test/fixtures/apps/ts/**/*.js test/fixtures/apps/connection-uri/**/*.js +test/fixtures/apps/typescript/**/*.js package-lock.json diff --git a/README.md b/README.md index ec27699..04ba266 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,25 @@ exports.sequelize = { }; ``` +- In case your project is typescript and you are going to use `sequelize-typescript` instead of `sequelize`, then edit your own configurations in `conif/config.{env}.ts` + + +```typescript +exports.sequelize = { + Sequelize: require('sequelize-typescript'), + dialect: 'mysql', // support: mysql, mariadb, postgres, mssql + database: 'test', + host: 'localhost', + port: 3306, + username: 'root', + password: '', + // delegate: 'myModel', // load all models to `app[delegate]` and `ctx[delegate]`, default to `model` + // baseDir: 'my_model', // load all files in `app/${baseDir}` as models, default to `model` + // exclude: 'index.js', // ignore `app/${baseDir}/index.js` when load models, support glob and array + // more sequelize options +}; +``` + You can also use the `connection uri` to configure the connection: ```js @@ -126,6 +145,7 @@ Define a model first. > NOTE: `options.delegate` default to `model`, so `app.model` is an [Instance of Sequelize](http://docs.sequelizejs.com/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor), so you can use methods like: `app.model.sync, app.model.query ...` +- Define model in plain js fashion: ```js // app/model/user.js @@ -160,6 +180,61 @@ module.exports = app => { ``` +- Define model in typescript fashion: +> Before you go this way, make sure your `tsconfig.json` contains the following: + +```json +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strictPropertyInitialization": false + } +} +``` + +```typescript +// app/model/user.ts + +import {Column, Model, Table, DataType} from 'sequelize-typescript' + +@Table({ + tableName: 'user', +}) +class User extends Model { + @Column(DataType.STRING) + login: string + + @Column(DataType.STRING(30)) + name: string + + @Column(DataType.STRING(32)) + password: string + + @Column(DataType.INTEGER) + age: number + + @Column(DataType.DATE) + last_sign_in_at: Date + + @Column(DataType.DATE) + created_at: Date + + @Column(DataType.DATE) + updated_at: Date + + static async findByLogin(login: string) { + return await this.findOne({where:{login}}) + } + + async logSignin() { + return await this.update({ last_sign_in_at: new Date() }); + } +} + +export default () => User +``` + Now you can use it in your controller: ```js @@ -301,7 +376,6 @@ module.exports = app => { }; ``` - ```js // app/controller/post.js class PostController extends Controller { @@ -360,6 +434,17 @@ Using [sequelize-cli](https://github.com/sequelize/cli) to help manage your data Please open an issue [here](https://github.com/eggjs/egg/issues). +## Contribution + +### Run test locally +Testing needs connecting to databases, on TravisCI this is addressed by claiming `mysql` services. To run test locally, `docker` is recommended installed first, then + +```shell script +sh test.sh +``` + +which will start a `mysql` container and create relevant databases. + ## License [MIT](LICENSE) diff --git a/index.d.ts b/index.d.ts index b1c57b3..0c3b80b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -19,9 +19,16 @@ interface EggSequelizeOptions extends sequelize.Options { /** * A full database URI * @example - * `connectionUri:"mysql://localhost:3306/database"` + * `connectionUri: "mysql://localhost:3306/database"` */ connectionUri?: string; + + /** + * Customized sequelize instance + * @example + * `Sequelize: require('sequelize-typescript').Sequelize` + */ + Sequelize?: any; } interface DataSources { diff --git a/lib/loader.js b/lib/loader.js index 2568699..9d8b2e3 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -102,7 +102,7 @@ module.exports = app => { caseStyle: 'upper', ignore: config.exclude, filter(model) { - if (!model || !model.sequelize) return false; + if (!model || (!model.sequelize && !model.findAll)) return false; models.push(model); return true; }, diff --git a/package.json b/package.json index 7dd3804..2a67c30 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "dependencies": { "@types/sequelize": "^4.27.24", "mz-modules": "^2.1.0", - "sequelize": "^5.0.0" + "sequelize": "^5.0.0", + "sequelize-typescript": "^1.0.0" }, "devDependencies": { "autod": "^3.0.1", @@ -24,7 +25,8 @@ "egg-mock": "^3.19.3", "eslint": "^5.3.0", "eslint-config-egg": "^7.0.0", - "mysql2": "^1.6.1" + "mysql2": "^1.6.1", + "reflect-metadata": "^0.1.13" }, "engines": { "node": ">=6.0.0" diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..a152537 --- /dev/null +++ b/test.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +container_name='local-mysql' + +if [ "$(docker inspect -f '{{.State.Running}}' $container_name)" = "true" ]; then + echo "$container_name is already running." +else + docker kill $container_name || echo "$container_name is already killed" + docker rm $container_name || echo "$container_name not exist" + docker run --name $container_name -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -d -p 3306:3306 mysql:5.7 + + sleep 10 +fi + +# shellcheck disable=SC2016 +mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS `test`;' +mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS `test1`;' +mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS `test2`;' +mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS `test3`;' +mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS `test4`;' +# shellcheck disable=SC2016 +echo 'databases is there' + +npm run test-local + +#!/usr/bin/env bash + +#docker kill local-mysql || echo "local-mysql is already killed" diff --git a/test/fixtures/apps/typescript/app/controller/home.ts b/test/fixtures/apps/typescript/app/controller/home.ts new file mode 100644 index 0000000..5736279 --- /dev/null +++ b/test/fixtures/apps/typescript/app/controller/home.ts @@ -0,0 +1,10 @@ +import { Controller } from 'egg'; + +export default class HomeController extends Controller { + async index() { + const { ctx } = this; + await ctx.model.Monkey.findUser(); + await ctx.model.User.associate(); + await ctx.model.User.test(); + } +} diff --git a/test/fixtures/apps/typescript/app/model/monkey.ts b/test/fixtures/apps/typescript/app/model/monkey.ts new file mode 100644 index 0000000..d1290be --- /dev/null +++ b/test/fixtures/apps/typescript/app/model/monkey.ts @@ -0,0 +1,27 @@ +import {Column, DataType, Model, Table} from "sequelize-typescript"; + +@Table({ + tableName: 'the_monkeys' +}) +class Monkey extends Model { + @Column({ + type: DataType.STRING, + allowNull: false + }) + name: string + + @Column({type: DataType.INTEGER}) + user_id: number + + @Column(DataType.DATE) + created_at: Date + + @Column(DataType.DATE) + updated_at: Date + + static async findUser() { + return this.findOne({where: {id: '123'}}) + } +} + +export default () => Monkey diff --git a/test/fixtures/apps/typescript/app/model/user.ts b/test/fixtures/apps/typescript/app/model/user.ts new file mode 100644 index 0000000..0c3277f --- /dev/null +++ b/test/fixtures/apps/typescript/app/model/user.ts @@ -0,0 +1,27 @@ +import {Application} from 'egg'; +import assert = require('assert'); +import {Column, DataType, Model, Table} from "sequelize-typescript"; + +@Table({tableName: 'user'}) +class User extends Model { + @Column(DataType.STRING(3)) + name: string + + @Column(DataType.INTEGER) + age: number +} + +export default function (app: Application) { + return class extends User { + static async associate() { + assert.ok(app.model.User); + } + + static async test() { + assert(app.config); + assert(app.model.User === this); + const monkey = await app.model.Monkey.create({name: 'The Monkey'}); + assert(monkey.isNewRecord === false); + } + } +} diff --git a/test/fixtures/apps/typescript/app/router.ts b/test/fixtures/apps/typescript/app/router.ts new file mode 100644 index 0000000..0a3a2f0 --- /dev/null +++ b/test/fixtures/apps/typescript/app/router.ts @@ -0,0 +1,7 @@ +import { Application } from 'egg'; + +export default (app: Application) => { + const { controller } = app; + + app.get('/', controller.home.index); +} diff --git a/test/fixtures/apps/typescript/config/config.default.ts b/test/fixtures/apps/typescript/config/config.default.ts new file mode 100644 index 0000000..6d1c119 --- /dev/null +++ b/test/fixtures/apps/typescript/config/config.default.ts @@ -0,0 +1,41 @@ +import * as path from 'path'; +import {EggAppInfo, EggAppConfig, PowerPartial} from 'egg'; + +export default (appInfo: EggAppInfo) => { + const config = {} as PowerPartial; + + config.keys = '123123'; + + config.sequelize = { + Sequelize: require('sequelize-typescript').Sequelize, + datasources: [ + { + delegate: 'model', + baseDir: 'model', + database: 'test', + dialect: 'mysql', + }, + { + delegate: 'sequelize', + baseDir: 'sequelize', + database: 'test1', + dialect: 'mysql', + exclude: 'Person.js', + }, + { + delegate: 'subproperty.a', + baseDir: 'subproperty/a', + database: 'test2', + dialect: 'mysql', + }, + { + delegate: 'subproperty.b', + baseDir: 'subproperty/b', + database: 'test3', + dialect: 'mysql', + }, + ] + } + + return config; +} diff --git a/test/fixtures/apps/typescript/package.json b/test/fixtures/apps/typescript/package.json new file mode 100644 index 0000000..e0e45a7 --- /dev/null +++ b/test/fixtures/apps/typescript/package.json @@ -0,0 +1,3 @@ +{ + "name": "typescript" +} diff --git a/test/fixtures/apps/typescript/tsconfig.json b/test/fixtures/apps/typescript/tsconfig.json new file mode 100644 index 0000000..40e5c02 --- /dev/null +++ b/test/fixtures/apps/typescript/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2017", + "module": "commonjs", + "moduleResolution": "node", + "baseUrl": "./", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strictPropertyInitialization": false, + "paths": { + "egg-sequelize": [ + "../../../../" + ] + } + } +} diff --git a/test/fixtures/apps/typescript/typings/index.d.ts b/test/fixtures/apps/typescript/typings/index.d.ts new file mode 100644 index 0000000..d57c40c --- /dev/null +++ b/test/fixtures/apps/typescript/typings/index.d.ts @@ -0,0 +1,16 @@ +import 'egg'; +import 'egg-sequelize'; +import HomeController from '../app/controller/home'; +import MonkeyModel from '../app/model/monkey'; +import UserModel from '../app/model/user'; + +declare module 'egg' { + interface IController { + home: HomeController; + } + + interface IModel { + Monkey: ReturnType; + User: ReturnType; + } +} \ No newline at end of file diff --git a/test/ts.test.js b/test/ts.test.js index 465b55d..856f0e0 100644 --- a/test/ts.test.js +++ b/test/ts.test.js @@ -2,6 +2,8 @@ const coffee = require('coffee'); const path = require('path'); +const mm = require('egg-mock'); +const assert = require('assert'); describe('typescript', () => { it('should compile ts without error', () => { @@ -14,3 +16,37 @@ describe('typescript', () => { .end(); }); }); + + +describe('typescript form 2', () => { + + let app; + + before(async () => { + await coffee.fork( + require.resolve('typescript/bin/tsc'), + [ '-p', path.resolve(__dirname, './fixtures/apps/typescript/tsconfig.json') ] + ) + .debug() + .expect('code', 0) + .end(); + + app = mm.app({ + baseDir: 'apps/typescript', + }); + + return app.ready(); + }); + + before(() => app.model.sync({ force: true })); + + after(mm.restore); + + it('should have models', async () => { + const ctx = app.mockContext(); + assert.ok(ctx.model); + assert.ok(ctx.model.User); + assert.ok(ctx.model.Monkey); + assert.ok(ctx.model !== app.model); + }); +}); From 331f268bb0b47d6f3fd5360981294e4c91f6bf0f Mon Sep 17 00:00:00 2001 From: Jeff Tian Date: Thu, 28 Nov 2019 15:36:46 +0800 Subject: [PATCH 2/3] fix: use sequelize.Sequelize type --- index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0c3b80b..23b403f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -24,11 +24,11 @@ interface EggSequelizeOptions extends sequelize.Options { connectionUri?: string; /** - * Customized sequelize instance + * support customizing sequelize class, default to `require('sequelize')` * @example * `Sequelize: require('sequelize-typescript').Sequelize` */ - Sequelize?: any; + Sequelize?: typeof sequelize.Sequelize; } interface DataSources { From a998a87609b3bf686fd6f86c3e2132299768d351 Mon Sep 17 00:00:00 2001 From: Jeff Tian Date: Thu, 28 Nov 2019 20:42:07 +0800 Subject: [PATCH 3/3] test: destroy scenarios --- test/fixtures/apps/typescript/app/model/monkey.ts | 9 +++++++-- test/fixtures/apps/typescript/app/model/user.ts | 14 +++++++++----- test/ts.test.js | 2 ++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/test/fixtures/apps/typescript/app/model/monkey.ts b/test/fixtures/apps/typescript/app/model/monkey.ts index d1290be..e7668cd 100644 --- a/test/fixtures/apps/typescript/app/model/monkey.ts +++ b/test/fixtures/apps/typescript/app/model/monkey.ts @@ -1,4 +1,5 @@ -import {Column, DataType, Model, Table} from "sequelize-typescript"; +import {Column, DataType, Model, Table, Sequelize} from "sequelize-typescript"; +import {Application} from "egg"; @Table({ tableName: 'the_monkeys' @@ -24,4 +25,8 @@ class Monkey extends Model { } } -export default () => Monkey +export default (_: Application, sequelize: Sequelize) => { + sequelize.addModels([Monkey]) + + return Monkey; +} diff --git a/test/fixtures/apps/typescript/app/model/user.ts b/test/fixtures/apps/typescript/app/model/user.ts index 0c3277f..0cf5e62 100644 --- a/test/fixtures/apps/typescript/app/model/user.ts +++ b/test/fixtures/apps/typescript/app/model/user.ts @@ -1,6 +1,6 @@ import {Application} from 'egg'; import assert = require('assert'); -import {Column, DataType, Model, Table} from "sequelize-typescript"; +import {Column, DataType, Model, Sequelize, Table} from "sequelize-typescript"; @Table({tableName: 'user'}) class User extends Model { @@ -11,8 +11,8 @@ class User extends Model { age: number } -export default function (app: Application) { - return class extends User { +export default function (app: Application, sequelize: Sequelize) { + const UserModel = class extends User { static async associate() { assert.ok(app.model.User); } @@ -21,7 +21,11 @@ export default function (app: Application) { assert(app.config); assert(app.model.User === this); const monkey = await app.model.Monkey.create({name: 'The Monkey'}); - assert(monkey.isNewRecord === false); + assert(!monkey.isNewRecord); } - } + }; + + sequelize.addModels([UserModel]) + + return UserModel } diff --git a/test/ts.test.js b/test/ts.test.js index 856f0e0..c7c6dd2 100644 --- a/test/ts.test.js +++ b/test/ts.test.js @@ -47,6 +47,8 @@ describe('typescript form 2', () => { assert.ok(ctx.model); assert.ok(ctx.model.User); assert.ok(ctx.model.Monkey); + const res = await app.model.User.destroy({ truncate: true, force: true }); + assert.ok(typeof res === 'number'); assert.ok(ctx.model !== app.model); }); });