├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── codeql.yml │ ├── nodejs.yml │ └── release.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Readme.md ├── Readme.zh-CN.md ├── agent.js ├── app.js ├── config └── config.default.js ├── examples ├── basic │ ├── app │ │ ├── controller │ │ │ └── users.js │ │ ├── model │ │ │ └── user.js │ │ └── router.js │ ├── config │ │ └── config.js │ └── package.json ├── delegate │ ├── app │ │ ├── controller │ │ │ └── users.js │ │ ├── model │ │ │ └── user.js │ │ └── router.js │ ├── config │ │ └── config.js │ └── package.json ├── sequelize │ ├── app │ │ ├── controller │ │ │ └── users.js │ │ ├── model │ │ │ ├── comment.js │ │ │ ├── common │ │ │ │ └── base.js │ │ │ ├── post.js │ │ │ └── user.js │ │ └── router.js │ ├── config │ │ └── config.js │ └── package.json └── typescript │ ├── basic │ ├── .eslintrc │ ├── app │ │ ├── controller │ │ │ └── users.ts │ │ ├── model │ │ │ ├── post.ts │ │ │ └── user.ts │ │ └── router.ts │ ├── config │ │ ├── config.ts │ │ └── plugin.ts │ ├── modules │ │ └── test-module │ │ │ ├── helloService.ts │ │ │ ├── package.json │ │ │ └── util.ts │ ├── package.json │ ├── tsconfig.json │ └── types │ │ └── index.d.ts │ └── sequelize │ ├── .eslintrc │ ├── .gitignore │ ├── app │ ├── controller │ │ └── users.ts │ ├── model │ │ ├── comment.ts │ │ ├── common │ │ │ └── base.ts │ │ ├── post.ts │ │ └── user.ts │ └── router.ts │ ├── config │ └── config.ts │ ├── package.json │ ├── tsconfig.json │ └── types │ └── index.d.ts ├── index.js ├── lib └── loader.js ├── package.json ├── test ├── basic.test.js ├── delegate.test.js ├── dumpfile.sql ├── prepare.sh ├── sequelize.test.js └── typescript │ ├── basic.test.ts │ └── sequelize.test.ts ├── tsconfig.json └── types └── index.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | *.ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg", 3 | "parser": "@babel/eslint-parser", 4 | "parserOptions": { 5 | "requireConfigFile": false 6 | }, 7 | "overrides": [{ 8 | "files": ["**/*.ts"], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "sourceType": "module" 12 | } 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ javascript ] 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v2 29 | with: 30 | languages: ${{ matrix.language }} 31 | queries: +security-and-quality 32 | 33 | - name: Autobuild 34 | uses: github/codeql-action/autobuild@v2 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v2 38 | with: 39 | category: "/language:${{ matrix.language }}" 40 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | services: 10 | # https://github.community/t5/GitHub-Actions/github-actions-cannot-connect-to-mysql-service/td-p/30611# 11 | # mysql:latest doesn't work out of the box yet (https://github.com/mysqljs/mysql/issues/2046) 12 | mysql: 13 | image: mysql:5.7 14 | env: 15 | MYSQL_ROOT_PASSWORD: password 16 | MYSQL_DATABASE: leoric 17 | ports: 18 | - 3306 19 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 20 | 21 | strategy: 22 | matrix: 23 | node-version: [16.x, 18.x, 20.x] 24 | 25 | steps: 26 | - uses: actions/checkout@v1 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - name: npm install, build, and test 32 | run: | 33 | npm install 34 | npm run build --if-present 35 | npm test 36 | env: 37 | CI: true 38 | MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }} 39 | MYSQL_ROOT_PASSWORD: password 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | release: 9 | name: Node.js 10 | uses: eggjs/github-actions/.github/workflows/node-release.yml@master 11 | secrets: 12 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 13 | GIT_TOKEN: ${{ secrets.GIT_TOKEN }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | node_modules 3 | run 4 | .vscode 5 | package-lock.json 6 | 7 | test/typescript/**/*.js 8 | test/typescript/**/*.js.map 9 | examples/typescript/**/*.js 10 | examples/typescript/**/*.js.map 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "lts/*" 5 | 6 | services: 7 | - "mysql" 8 | 9 | after_success: 10 | - npm run cov 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.4.1](https://github.com/eggjs/egg-orm/compare/v2.4.0...v2.4.1) (2024-04-15) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * add repository url ([1146f45](https://github.com/eggjs/egg-orm/commit/1146f45a353090f02b61b8e597db16ecf2ac4e19)) 9 | 10 | ## [2.4.0](https://github.com/eggjs/egg-orm/compare/v2.3.1...v2.4.0) (2024-04-15) 11 | 12 | 13 | ### Features 14 | 15 | * devDeps mysql2 >= 3.9.4 ([#35](https://github.com/eggjs/egg-orm/issues/35)) ([af13a32](https://github.com/eggjs/egg-orm/commit/af13a320ff499f4236289a4901f90ee3c4dc6215)) 16 | 17 | --- 18 | 19 | 2.3.1 / 2023-12-19 20 | ================== 21 | 22 | ## What's Changed 23 | * Add CodeQL workflow for GitHub code scanning by @lgtm-com in https://github.com/eggjs/egg-orm/pull/32 24 | * chore: overwrite the default console.error in logQueryError by @cyjake in https://github.com/eggjs/egg-orm/pull/33 25 | 26 | ## New Contributors 27 | * @lgtm-com made their first contribution in https://github.com/eggjs/egg-orm/pull/32 28 | 29 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.3.0...v2.3.1 30 | 31 | 2.3.0 / 2022-11-09 32 | ================== 33 | 34 | ## What's Changed 35 | * feat: rewire prototype of models when subclassing is enabled by @cyjake in https://github.com/eggjs/egg-orm/pull/29 36 | 37 | 38 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.2.0...v2.3.0 39 | 40 | 2.2.0 / 2022-09-08 41 | ================== 42 | 43 | ## What's Changed 44 | * feat: support tegg-egg mixing mode by @JimmyDaddy in https://github.com/eggjs/egg-orm/pull/28 45 | 46 | 47 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.1.2...v2.2.0 48 | 49 | 2.1.2 / 2022-08-31 50 | ================== 51 | 52 | ## What's Changed 53 | * chore: sequelizeBone export app/ctx, doc and example complete by @JimmyDaddy in https://github.com/eggjs/egg-orm/pull/27 54 | 55 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.1.1...v2.1.2 56 | 57 | 2.1.1 / 2022-08-25 58 | ================== 59 | 60 | ## What's Changed 61 | * docs: ts guide doc by @JimmyDaddy in https://github.com/eggjs/egg-orm/pull/23 62 | * fix: override model.app or model.prototype.app event if exists by @cyjake in https://github.com/eggjs/egg-orm/pull/24 63 | * fix: sequelize adapter with model extends from Bone directly and export leoric form egg-orm by @JimmyDaddy in https://github.com/eggjs/egg-orm/pull/26 64 | 65 | 66 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.1.0...v2.1.1 67 | 68 | 2.1.0 / 2022-08-24 69 | ================== 70 | 71 | ## What's Changed 72 | * feat: add index.d.ts by @luckydrq in https://github.com/eggjs/egg-orm/pull/17 73 | * test: declaration types integration test by @cyjake in https://github.com/eggjs/egg-orm/pull/18 74 | * chore: elaborate example apps by @cyjake in https://github.com/eggjs/egg-orm/pull/19 75 | * docs: zh readme refabrication by @cyjake in https://github.com/eggjs/egg-orm/pull/20 76 | * fix: readme by @leoner in https://github.com/eggjs/egg-orm/pull/21 77 | * feat: support ts and class model definition by @JimmyDaddy in https://github.com/eggjs/egg-orm/pull/22 78 | 79 | ## New Contributors 80 | * @luckydrq made their first contribution in https://github.com/eggjs/egg-orm/pull/17 81 | * @leoner made their first contribution in https://github.com/eggjs/egg-orm/pull/21 82 | 83 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v2.0.0...v2.1.0 84 | 85 | 2.0.0 / 2022-01-06 86 | ================== 87 | 88 | ## What's Changed 89 | * upgrade: leoric v2.x by @cyjake in https://github.com/eggjs/egg-orm/pull/16 90 | 91 | 92 | **Full Changelog**: https://github.com/eggjs/egg-orm/compare/v1.1.7...v2.0.0 93 | 94 | 1.1.7 / 2021-07-09 95 | ================== 96 | 97 | **fixes** 98 | * [#15](https://github.com/eggjs/egg-orm/pull/15) - fix: `Model.name` should be consistent (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) 99 | 100 | 1.1.6 / 2021-07-06 101 | ================== 102 | 103 | **fixes** 104 | * [#14](https://github.com/eggjs/egg-orm/pull/14) - fix: ignore properties injected by egg loader (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) 105 | 106 | 1.1.5 / 2021-07-01 107 | ================== 108 | 109 | **fixes** 110 | * [#13](https://github.com/eggjs/egg-orm/pull/13) - fix: support Model.app and model.app (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) 111 | 112 | 1.1.4 / 2021-06-25 113 | ================== 114 | 115 | **fixes** 116 | * [#12](https://github.com/eggjs/egg-orm/pull/12) - fix: allow models be extended from Bone directly to make codebases that use v0.4.x a bit easier to upgrade (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) 117 | 118 | 1.1.3 / 2021-06-16 119 | ================== 120 | 121 | **fixes** 122 | * [#9](https://github.com/eggjs/egg-orm/pull/9) - fix: ctx.model.ctx injection (Chen Yangjian <<252317+cyjake@users.noreply.github.com>>) 123 | 124 | 1.1.2 / 2021-03-04 125 | ================== 126 | 127 | **fixes** 128 | * [#7](https://github.com/eggjs/egg-orm/pull/7) - fix: ctx is undefined while custom setter executing (jimmydaddy <>) 129 | 130 | 1.1.1 / 2020-11-30 131 | ================== 132 | 133 | **fixes** 134 | * [#5](https://github.com/eggjs/egg-orm/pull/5) - fix: realm.define (jimmydaddy <>) 135 | 136 | 1.1.0 / 2020-08-12 137 | ================== 138 | 139 | **fixes** 140 | * [[`816c27e`](http://github.com/eggjs/egg-orm/commit/816c27ef33b8fc19e43cb0dfa835ff737c8f3551)] - fix delegate and lazy load database (#4) (Yiyu He <>) 141 | 142 | 1.0.0 / 2020-02-24 143 | ================== 144 | 145 | **fixes** 146 | * [[`5a8f304`](http://github.com/eggjs/egg-orm/commit/5a8f304177d59381391e890d92f9e7acd923ca76)] - fix: run in sequelize mode (#3) (fengmk2 <>),fatal: No names found, cannot describe anything. 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present Alibaba Group Holding Limited and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # egg-orm 2 | 3 | [中文介绍](Readme.zh-CN.md) 4 | 5 | Yet another object-relational mapping plugin for Egg, which is based on [Leoric](https://leoric.js.org). 6 | 7 | ## Install 8 | 9 | ```bash 10 | $ npm i --save egg-orm 11 | $ npm install --save mysql # MySQL or compatible dialects 12 | 13 | # Or use other database backend. 14 | $ npm install --save pg # PostgreSQL 15 | $ npm install --save sqlite3 # SQLite 16 | ``` 17 | 18 | ## Usage 19 | 20 | With egg-orm you can define models in `app/model` in JavaScript: 21 | 22 | ```js 23 | // app/model/user.js 24 | module.exports = function(app) { 25 | const { Bone, DataTypes: { STRING } } = app.model; 26 | 27 | return class User extends Bone { 28 | static table = 'users' 29 | 30 | static attributes = { 31 | name: STRING, 32 | password: STRING, 33 | avatar: STRING(2048), 34 | } 35 | }); 36 | } 37 | ``` 38 | 39 | or in TypeScript: 40 | 41 | ```ts 42 | // app/model/post.ts 43 | import { Column, Bone, BelongsTo, DataTypes } from 'egg-orm'; 44 | import User from './user'; 45 | 46 | export default class Post extends Bone { 47 | @Column({ primaryKey: true }) 48 | id: bigint; 49 | 50 | @Column(DataTypes.TEXT) 51 | content: string; 52 | 53 | @Column() 54 | description: string; 55 | 56 | @Column() 57 | userId: bigint; 58 | 59 | @BelongsTo() 60 | user: User; 61 | } 62 | 63 | // app/model/user.ts 64 | import { Column, Bone, HasMany } from 'egg-orm'; 65 | import Post from './post'; 66 | 67 | export default class User extends Bone { 68 | @Column({ allowNull: false }) 69 | nickname: string; 70 | 71 | @Column() 72 | email: string; 73 | 74 | @Column() 75 | createdAt: Date; 76 | 77 | @HasMany() 78 | posts: Post[]; 79 | } 80 | 81 | ``` 82 | 83 | and use them like below: 84 | 85 | ```js 86 | // app/controller/home.js 87 | const { Controller } = require('egg'); 88 | module.exports = class HomeController extends Controller { 89 | async index() { 90 | const users = await ctx.model.User.find({ 91 | corpId: ctx.model.Corp.findOne({ name: 'tyrael' }), 92 | }); 93 | ctx.body = users; 94 | } 95 | }; 96 | ``` 97 | 98 | ## Configuration 99 | 100 | Firstly, enable egg-orm plugin: 101 | 102 | ```js 103 | // config/plugin.js 104 | exports.orm = { 105 | enable: true, 106 | package: 'egg-orm', 107 | }; 108 | ``` 109 | 110 | Secondly, configure the plugin accordingly: 111 | 112 | ```js 113 | // config/config.default.js 114 | exports.orm = { 115 | client: 'mysql', 116 | database: 'temp', 117 | host: 'localhost', 118 | baseDir: 'model', 119 | }; 120 | ``` 121 | 122 | In this example above, we're accessing the `temp` database of MySQL via `localhost` with the models defined in directory `app/model`. For more information, please refer to [Setup Leoric in Egg](https://leoric.js.org/setup/egg). 123 | 124 | ## License 125 | 126 | [MIT](LICENSE) 127 | -------------------------------------------------------------------------------- /Readme.zh-CN.md: -------------------------------------------------------------------------------- 1 | # egg-orm 2 | 3 | egg-orm 是一个适用于 Egg 框架的数据模型层插件,支持 JavaScript 和 TypeScript,基于 [Leoric](https://leoric.js.org/zh) 对象关系映射,可以通过 [examples 中的示例项目](https://github.com/eggjs/egg-orm/tree/master/examples) 来快速了解如何在 Egg 应用中配置和使用 egg-orm 插件。 4 | 5 | ## 安装 6 | 7 | ```bash 8 | $ npm i --save egg-orm 9 | $ npm install --save mysql # MySQL 或者其他兼容 MySQL 的数据库 10 | 11 | # 其他数据库类型 12 | $ npm install --save pg # PostgreSQL 13 | $ npm install --save sqlite3 # SQLite 14 | ``` 15 | 16 | ## 使用 17 | 18 | 开启 egg-orm 插件即可在 `app/model` 中定义数据模型: 19 | 20 | ```js 21 | // app/model/user.js 22 | module.exports = function(app) { 23 | const { Bone, DataTypes: { STRING } } = app.model; 24 | 25 | return class User extends Bone { 26 | static table = 'users' 27 | 28 | static attributes = { 29 | name: STRING, 30 | password: STRING, 31 | avatar: STRING(2048), 32 | } 33 | }); 34 | } 35 | ``` 36 | 37 | 也支持试用 TypeScript 编写: 38 | 39 | ```ts 40 | // app/model/post.ts 41 | import { Column, Bone, BelongsTo, DataTypes } from 'egg-orm'; 42 | import User from './user'; 43 | 44 | export default class Post extends Bone { 45 | @Column({ primaryKey: true }) 46 | id: bigint; 47 | 48 | @Column(DataTypes.TEXT) 49 | content: string; 50 | 51 | @Column() 52 | description: string; 53 | 54 | @Column() 55 | userId: bigint; 56 | 57 | @BelongsTo() 58 | user: User; 59 | } 60 | 61 | // app/model/user.ts 62 | import { Column, Bone, HasMany } from 'egg-orm'; 63 | import Post from './post'; 64 | 65 | export default class User extends Bone { 66 | @Column({ allowNull: false }) 67 | nickname: string; 68 | 69 | @Column() 70 | email: string; 71 | 72 | @Column() 73 | createdAt: Date; 74 | 75 | @HasMany() 76 | posts: Post[]; 77 | } 78 | ``` 79 | 80 | 在 Controller 调用: 81 | 82 | ```js 83 | // app/controller/home.js 84 | const { Controller } = require('egg'); 85 | module.exports = class HomeController extends Controller { 86 | async index() { 87 | const users = await ctx.model.User.find({ 88 | corpId: ctx.model.Corp.findOne({ name: 'tyrael' }), 89 | }); 90 | ctx.body = users; 91 | } 92 | }; 93 | ``` 94 | 95 | ## 配置 96 | 97 | 首先开启(并安装) egg-orm 插件 98 | 99 | ```js 100 | // config/plugin.js 101 | exports.orm = { 102 | enable: true, 103 | package: 'egg-orm', 104 | }; 105 | ``` 106 | 107 | 然后按需配置数据库: 108 | 109 | ```js 110 | // config/config.default.js 111 | exports.orm = { 112 | client: 'mysql', 113 | database: 'temp', 114 | host: 'localhost', 115 | baseDir: 'model', 116 | }; 117 | ``` 118 | 119 | 在上面这个例子中,我们将数据模型定义文件放在 `app/model` 目录,通过 `localhost` 访问 MySQL 中的 `temp` 数据库。推荐阅读 [Egg 应用配置指南](https://leoric.js.org/zh/setup/egg)一文了解更多有关在 Egg 中使用 egg-orm 的帮助文档。 120 | 121 | ## 迁移任务 122 | 123 | egg-orm 支持两种在开发模式维护表结构的方式: 124 | 125 | - 类似 Django 的数据模型层,基于模型属性定义自动同步到数据库 `app.model.sync()` 126 | - 类似 Ruby on Rails 的 Active Record,使用迁移任务手动管理表结构 127 | 128 | 前者比较简单,可以直接在 Egg 应用的 app.js 在 didReady 阶段执行即可: 129 | 130 | ```js 131 | // app.js 132 | module.exports = class AppBootHook { 133 | async didReady() { 134 | const { app } = this; 135 | if (app.config.env === 'local') { 136 | // ⚠️ 此操作可能导致数据丢失,请务必谨慎使用 137 | await app.model.sync({ alter: true }); 138 | } 139 | } 140 | } 141 | ``` 142 | 143 | 因为 `app.model.sync()` 的风险性,使用迁移任务来管理表结构是更为稳妥的方式,在多人协作时也更加方便。目前 egg-orm 还没有提供命令封装,可以调用如下 API 来使用迁移任务: 144 | 145 | - 创建迁移任务 `app.model.createMigrationFile()` 146 | - 执行迁移任务 `app.model.migrate(step)` 147 | - 回滚迁移任务 `app.model.rollback(step)` 148 | 149 | 详细的使用说明可以参考《[迁移任务](https://leoric.js.org/zh/migrations.html)》帮助文档。 150 | 151 | ## 授权许可 152 | 153 | [MIT](LICENSE) 154 | -------------------------------------------------------------------------------- /agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/loader'); 4 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/loader'); 4 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const debug = require('debug')('egg-orm'); 4 | 5 | exports.orm = { 6 | client: 'mysql', 7 | database: 'test', 8 | host: 'localhost', 9 | port: 3306, 10 | user: 'root', 11 | 12 | delegate: 'model', 13 | baseDir: 'model', 14 | migrations: 'database', 15 | 16 | define: { 17 | underscored: true, 18 | }, 19 | 20 | logger: { 21 | logQuery(sql, duration, options) { 22 | debug('[query] [%s] %s', duration, sql); 23 | }, 24 | logQueryError(err, sql, duration, options) { 25 | debug('[query] [%s] %s', duration, sql, err); 26 | } 27 | } 28 | 29 | // or put your config into datasources array to connect multiple databases 30 | // datasources: [], 31 | }; 32 | -------------------------------------------------------------------------------- /examples/basic/app/controller/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | return class UsersController extends app.Controller { 5 | async show() { 6 | const user = await this.ctx.model.User.findOne(this.ctx.params.id); 7 | this.ctx.body = user; 8 | } 9 | 10 | async create() { 11 | const user = await app.model.User.create({ 12 | nickname: this.ctx.request.body.nickname, 13 | email: this.ctx.request.body.email, 14 | }); 15 | this.ctx.body = user; 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /examples/basic/app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Bone } = require('../../../../'); 4 | 5 | module.exports = class User extends Bone {}; 6 | -------------------------------------------------------------------------------- /examples/basic/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | app.resources('users', '/users', 'users'); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/basic/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Bone } = require('leoric'); 4 | 5 | exports.orm = { 6 | database: 'egg-orm', 7 | port: process.env.MYSQL_PORT, 8 | // connect to Bone directly 9 | Bone, 10 | }; 11 | 12 | exports.keys = 'hello'; 13 | 14 | exports.security = { 15 | csrf: false, 16 | }; 17 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "legacy" 3 | } 4 | -------------------------------------------------------------------------------- /examples/delegate/app/controller/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | return class UsersController extends app.Controller { 5 | async show() { 6 | const user = await this.ctx.orm.User.findByPk(this.ctx.params.id); 7 | this.ctx.body = user; 8 | } 9 | 10 | async create() { 11 | const user = await app.orm.User.create({ 12 | nickname: this.ctx.request.body.nickname, 13 | email: this.ctx.request.body.email, 14 | }); 15 | this.ctx.body = user; 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /examples/delegate/app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (app) => { 4 | const { Bone, DataTypes: { STRING, DATE } } = app.orm; 5 | 6 | class User extends Bone { 7 | static attributes = { 8 | nickname: { type: STRING, allowNull: false }, 9 | email: { type: STRING, allowNull: true }, 10 | createdAt: { type: DATE }, 11 | } 12 | } 13 | 14 | return User; 15 | }; 16 | -------------------------------------------------------------------------------- /examples/delegate/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | app.resources('users', '/users', 'users'); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/delegate/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.orm = { 4 | database: 'egg-orm', 5 | sequelize: true, 6 | delegate: 'orm', 7 | port: process.env.MYSQL_PORT, 8 | }; 9 | 10 | exports.keys = 'hello'; 11 | 12 | exports.security = { 13 | csrf: false, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/delegate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delegate" 3 | } 4 | -------------------------------------------------------------------------------- /examples/sequelize/app/controller/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | return class UsersController extends app.Controller { 5 | async show() { 6 | const user = await this.ctx.model.User.findByPk(this.ctx.params.id); 7 | this.ctx.body = user; 8 | } 9 | 10 | async create() { 11 | const user = await app.model.User.create({ 12 | nickname: this.ctx.request.body.nickname, 13 | email: this.ctx.request.body.email, 14 | }); 15 | this.ctx.body = user; 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /examples/sequelize/app/model/comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { DataTypes: { BIGINT, TEXT } } = require('../../../..'); 4 | const Base = require('./common/base'); 5 | 6 | module.exports = class Comment extends Base { 7 | static attributes = { 8 | id: { type: BIGINT, primaryKey: true, autoIncrement: true }, 9 | content: { type: TEXT }, 10 | userId: { type: BIGINT, allowNull: false }, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/sequelize/app/model/common/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Bone } = require('../../../../..'); 4 | 5 | module.exports = class Base extends Bone { 6 | static shardingKey = 'userId'; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/sequelize/app/model/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | const { DataTypes: { BIGINT, TEXT, STRING } } = app.model; 5 | const Post = app.model.define('Post', { 6 | id: { type: BIGINT, autoIncrement: true }, 7 | content: TEXT, 8 | description: STRING, 9 | userId: { type: BIGINT, allowNull: false }, 10 | }); 11 | return Post; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/sequelize/app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { Bone, DataTypes: { STRING, DATE } } = app.model; 5 | 6 | class User extends Bone { 7 | static attributes = { 8 | nickname: { type: STRING, allowNull: false }, 9 | email: { type: STRING, allowNull: true }, 10 | createdAt: { type: DATE }, 11 | }; 12 | } 13 | 14 | return User; 15 | }; 16 | -------------------------------------------------------------------------------- /examples/sequelize/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | app.resources('users', '/users', 'users'); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/sequelize/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.orm = { 4 | database: 'egg-orm', 5 | sequelize: true, 6 | port: process.env.MYSQL_PORT, 7 | exclude: 'common', 8 | }; 9 | 10 | exports.keys = 'hello'; 11 | 12 | exports.security = { 13 | csrf: false, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic" 3 | } 4 | -------------------------------------------------------------------------------- /examples/typescript/basic/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "sourceType": "module" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/typescript/basic/app/controller/users.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "egg"; 2 | 3 | export default class UsersController extends Controller { 4 | async show() { 5 | // TODO: sequelize adapter methods like findByPk() isn't provided by leoric yet 6 | // const user = await this.ctx.model.User.findByPk(this.ctx.params.id); 7 | const user = await this.ctx.model.User.findOne(this.ctx.params.id); 8 | this.ctx.body = user; 9 | } 10 | 11 | async create() { 12 | const user = await this.app.model.User.create({ 13 | nickname: this.ctx.request.body.nickname, 14 | email: this.ctx.request.body.email, 15 | }); 16 | this.ctx.body = user; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /examples/typescript/basic/app/model/post.ts: -------------------------------------------------------------------------------- 1 | import { Column, Bone, BelongsTo, DataTypes } from '../../../../../'; 2 | import User from './user'; 3 | 4 | export default class Post extends Bone { 5 | @Column({ primaryKey: true }) 6 | id: bigint; 7 | 8 | @Column(DataTypes.TEXT) 9 | content: string; 10 | 11 | @Column() 12 | get description(): string { 13 | return (this.attribute('description') as string) || 'defaultDesc'; 14 | } 15 | 16 | @Column() 17 | createdAt: Date; 18 | 19 | @Column() 20 | userId: bigint; 21 | 22 | @BelongsTo() 23 | user: User; 24 | } 25 | -------------------------------------------------------------------------------- /examples/typescript/basic/app/model/user.ts: -------------------------------------------------------------------------------- 1 | import { Column, Bone, HasMany } from '../../../../../'; 2 | import Post from './post'; 3 | 4 | export default class User extends Bone { 5 | @Column({ allowNull: false }) 6 | nickname: string; 7 | 8 | @Column() 9 | email: string; 10 | 11 | @Column() 12 | createdAt: Date; 13 | 14 | @HasMany() 15 | posts: Post[]; 16 | } 17 | -------------------------------------------------------------------------------- /examples/typescript/basic/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from "egg"; 2 | 3 | export default function(app: Application) { 4 | app.resources('users', '/users', 'users'); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/typescript/basic/config/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | orm: { 3 | database: 'egg-orm', 4 | port: process.env.MYSQL_PORT, 5 | }, 6 | 7 | keys: 'hello', 8 | 9 | security: { 10 | csrf: false, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /examples/typescript/basic/config/plugin.ts: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | tegg: { 4 | package: '@eggjs/tegg-plugin', 5 | enable: true, 6 | }, 7 | teggConfig: { 8 | package: '@eggjs/tegg-config', 9 | enable: true, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /examples/typescript/basic/modules/test-module/helloService.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ContextProto, AccessLevel } from '@eggjs/tegg'; 3 | import User from '../../app/model/user'; 4 | 5 | import InjectModel from './util'; 6 | 7 | @ContextProto({ 8 | accessLevel: AccessLevel.PUBLIC, 9 | }) 10 | export default class HelloService { 11 | @InjectModel() 12 | private readonly User: typeof User; 13 | 14 | async sayHello(id: number) { 15 | const user = await this.User.findOne(id); 16 | return `hello ${user.nickname}`; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/typescript/basic/modules/test-module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-module", 3 | "description": "test module", 4 | "eggModule": { 5 | "name": "testModule" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/typescript/basic/modules/test-module/util.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Inject } from '@eggjs/tegg'; 3 | import { IModel } from 'egg'; 4 | 5 | export type InjectParam = { 6 | name?: keyof IModel; 7 | }; 8 | 9 | function injectFactory(targetKey, targetName) { 10 | return (param?: InjectParam) => (target, key: string) => { 11 | if (!target[targetKey]) { 12 | Inject({ name: targetName })(target, targetKey); 13 | } 14 | 15 | Object.defineProperty(target, targetKey, { 16 | enumerable: false, 17 | configurable: false, 18 | }); 19 | 20 | Object.defineProperty(target, key, { 21 | get() { 22 | return this[targetKey][param?.name || key]; 23 | }, 24 | enumerable: true, 25 | configurable: true, 26 | }); 27 | } 28 | } 29 | 30 | const modelMetaKey = Symbol.for('model'); 31 | 32 | export default function InjectModel(param?: InjectParam){ 33 | return injectFactory(modelMetaKey, 'model')(param); 34 | } 35 | -------------------------------------------------------------------------------- /examples/typescript/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-orm-typescript-standard", 3 | "types": "types/index.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /examples/typescript/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "moduleResolution": "Node", 5 | "module": "CommonJS", 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": [ 13 | "types", 14 | "./" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/typescript/basic/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import 'egg'; 2 | 3 | import Post from '../app/model/post'; 4 | import User from '../app/model/user'; 5 | import HelloService from '../modules/test-module/helloService'; 6 | 7 | 8 | declare module 'egg' { 9 | interface IModel { 10 | Post: typeof Post; 11 | User: typeof User; 12 | } 13 | 14 | interface Application { 15 | model: IModel; 16 | } 17 | 18 | // extend context 19 | interface Context { 20 | model: IModel; 21 | } 22 | 23 | export interface EggModule { 24 | hello: { 25 | helloService: HelloService; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "sourceType": "module" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/controller/users.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "egg"; 2 | 3 | export default class UsersController extends Controller { 4 | async show() { 5 | const user = await this.ctx.model.User.findOne({ 6 | where: { 7 | id: this.ctx.params.id, 8 | } 9 | }); 10 | 11 | this.ctx.body = user; 12 | } 13 | 14 | async create() { 15 | const user = await this.app.model.User.create({ 16 | nickname: this.ctx.request.body.nickname, 17 | email: this.ctx.request.body.email, 18 | }); 19 | this.ctx.body = user; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/model/comment.ts: -------------------------------------------------------------------------------- 1 | import { Column, DataTypes } from '../../../../..'; 2 | import Base from './common/base'; 3 | 4 | export default class Comment extends Base { 5 | static shardingKey = 'userId'; 6 | 7 | @Column(DataTypes.TEXT) 8 | content: string; 9 | 10 | @Column({ allowNull: false }) 11 | user_id: bigint; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/model/common/base.ts: -------------------------------------------------------------------------------- 1 | import { SequelizeBone, Column, DataTypes } from '../../../../../..'; 2 | 3 | export default class Base extends SequelizeBone { 4 | 5 | @Column({ primaryKey: true, autoIncrement: true, type: DataTypes.BIGINT }) 6 | id: number; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/model/post.ts: -------------------------------------------------------------------------------- 1 | import { Column, BelongsTo, DataTypes } from '../../../../../'; 2 | import Base from './common/base'; 3 | import User from './user'; 4 | 5 | export default class Post extends Base { 6 | 7 | @Column(DataTypes.TEXT) 8 | content: string; 9 | 10 | @Column({ defaultValue: 'defaultDesc' }) 11 | get description(): string { 12 | return this.attribute('description') || 'defaultDesc'; 13 | } 14 | 15 | @Column() 16 | created_at: Date; 17 | 18 | @Column() 19 | user_id: bigint; 20 | 21 | @BelongsTo() 22 | user: User; 23 | } 24 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/model/user.ts: -------------------------------------------------------------------------------- 1 | import { Column, HasMany, DataTypes } from '../../../../../'; 2 | import Base from './common/base'; 3 | import Post from './post'; 4 | 5 | export default class User extends Base { 6 | 7 | @Column({ allowNull: false }) 8 | nickname: string; 9 | 10 | @Column() 11 | email: string; 12 | 13 | @Column() 14 | created_at: Date; 15 | 16 | @HasMany() 17 | posts: Post[]; 18 | 19 | @Column(DataTypes.VIRTUAL) 20 | get userAppName(): string { 21 | return `${this.app.name}${this.nickname}` 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from "egg"; 2 | 3 | export default function(app: Application) { 4 | app.resources('users', '/users', 'users'); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/config/config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | orm: { 3 | database: 'egg-orm', 4 | port: process.env.MYSQL_PORT, 5 | exclude: 'common', 6 | sequelize: true, 7 | }, 8 | 9 | keys: 'hello', 10 | 11 | security: { 12 | csrf: false, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-orm-typescript-sequelize", 3 | "types": "types/index.d.ts" 4 | } 5 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "moduleResolution": "Node", 5 | "module": "CommonJS", 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": [ 13 | "types", 14 | "./" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/typescript/sequelize/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import Post from '../app/model/post'; 2 | import User from '../app/model/user'; 3 | 4 | declare module 'egg' { 5 | interface IModel { 6 | Post: typeof Post; 7 | User: typeof User; 8 | } 9 | 10 | interface Application { 11 | model: IModel; 12 | } 13 | 14 | // extend context 15 | interface Context { 16 | model: IModel; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('leoric'); 4 | -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const Realm = require('leoric'); 5 | 6 | module.exports = class BootHook { 7 | constructor(app) { 8 | this.app = app; 9 | this.databases = []; 10 | } 11 | 12 | // load models to app in didLoad so that tegg can read the delegate in app/context 13 | // developer can change the config in configDidLoad method 14 | async didLoad() { 15 | const { app } = this; 16 | const config = app.config.orm; 17 | const datasources = config.datasources || [ config ]; 18 | // Make multiple copies of Bone to support multiple databases, otherwise use Bone directly 19 | const subclass = datasources.length > 1; 20 | this.databases = datasources.map(datasource => { 21 | return loadDatabase(app, { subclass, ...datasource }); 22 | }); 23 | } 24 | 25 | async willReady() { 26 | await Promise.all(this.databases.map(authenticate)); 27 | } 28 | }; 29 | 30 | function createProxy(realm, injects) { 31 | return new Proxy(realm, { 32 | get(target, property) { 33 | const injected = this[property]; 34 | if (injected) return injected; 35 | 36 | // ctx.model.ctx 37 | if (injects.hasOwnProperty(property)) return injects[property]; 38 | 39 | const OriginModelClass = target[property]; 40 | // ctx.model.DataTypes 41 | if (!(OriginModelClass && Realm.isBone(OriginModelClass))) { 42 | return OriginModelClass; 43 | } 44 | 45 | class InjectModelClass extends OriginModelClass { 46 | static get name() { 47 | return super.name; 48 | } 49 | } 50 | for (const key of Object.keys(injects)) { 51 | const value = injects[key]; 52 | for (const target of [ InjectModelClass, InjectModelClass.prototype ]) { 53 | Object.defineProperty(target, key, { 54 | get() { 55 | return value; 56 | }, 57 | }); 58 | } 59 | } 60 | // stash injected class onto ctx.model 61 | this[property] = InjectModelClass; 62 | return InjectModelClass; 63 | }, 64 | }); 65 | } 66 | 67 | function injectProxy(app, delegate, realm) { 68 | const PROXY = Symbol('egg-orm:proxy'); 69 | 70 | Object.defineProperty(app, delegate, { 71 | get() { 72 | return realm; 73 | }, 74 | }); 75 | Object.defineProperty(realm, 'app', { 76 | get() { 77 | return app; 78 | }, 79 | }); 80 | 81 | Object.defineProperty(app.context, delegate, { 82 | get() { 83 | if (this[PROXY]) return this[PROXY]; 84 | const ctx = this; 85 | const proxy = createProxy(realm, { app: ctx.app, ctx }); 86 | this[PROXY] = proxy; 87 | return proxy; 88 | }, 89 | }); 90 | } 91 | 92 | function loadDatabase(app, config) { 93 | const modelDir = path.join(app.baseDir, 'app', config.baseDir); 94 | const target = Symbol(config.delegate); 95 | const models = []; 96 | const { delegate, ...options } = config; 97 | const realm = new Realm({ ...options, models }); 98 | const { Bone: Model } = realm; 99 | 100 | // be compatible with egg-sequelize 101 | realm.Model = Model; 102 | injectProxy(app, delegate, realm); 103 | // const Bone = config.sequelize? Realm.SequelizeBone : Realm.Bone; 104 | 105 | app.loader.loadToApp(modelDir, target, { 106 | caseStyle: 'upper', 107 | ignore: config.exclude, 108 | initializer(factory) { 109 | // module.exports = function() { 110 | // return Realm.define('name', props); 111 | // }; 112 | if (typeof factory === 'function' && !Realm.isBone(factory)) { 113 | factory = factory(app, Model); 114 | } 115 | if (!Realm.isBone(factory)) return; 116 | 117 | // class User extends require('leoric').Bone; 118 | // class User extends (class extends require('leoric').Bone {}); 119 | let klass = factory; 120 | while (klass !== null) { 121 | const temp = Object.getPrototypeOf(klass); 122 | // TODO need a new major version that make base Bone specific in code, sequelize use SequelizeBone, otherwise use Bone 123 | if (temp === Realm.Bone || temp === Realm.SequelizeBone || temp === Model) { 124 | break; 125 | } 126 | klass = temp; 127 | } 128 | if (klass !== Model && !(klass instanceof Model) && Object.getPrototypeOf(klass) !== Model) { 129 | Object.setPrototypeOf(klass, Model); 130 | Object.setPrototypeOf(klass.prototype, Object.create(Model.prototype)); 131 | } 132 | 133 | const name = factory.name; 134 | Object.defineProperty(factory, 'name', { value: name }); 135 | 136 | realm[name] = factory; 137 | return factory; 138 | }, 139 | filter(model) { 140 | if (Realm.isBone(model)) { 141 | models.push(model); 142 | return true; 143 | } 144 | }, 145 | }); 146 | 147 | for (const model of models) { 148 | // make `fullPath` and `pathName` not enumerable to ignore them when serialize 149 | for (const key of [ 'fullPath', 'pathName' ]) { 150 | const descriptor = Object.getOwnPropertyDescriptor(model.prototype, key); 151 | if (descriptor && typeof descriptor.value === 'string') { 152 | Object.defineProperty(model.prototype, key, { 153 | ...descriptor, 154 | writable: true, 155 | enumerable: false, 156 | configurable: true, 157 | }); 158 | } 159 | } 160 | 161 | realm[model.name] = model; 162 | } 163 | for (const target of [ Model, Model.prototype ]) { 164 | Object.defineProperty(target, 'app', { 165 | get() { 166 | return app; 167 | }, 168 | configurable: true, 169 | enumerable: false, 170 | }); 171 | } 172 | 173 | return realm; 174 | } 175 | 176 | async function authenticate(realm) { 177 | await realm.connect(); 178 | } 179 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-orm", 3 | "version": "2.4.1", 4 | "description": "Object relational mapping for Egg applications", 5 | "eggPlugin": { 6 | "name": "orm" 7 | }, 8 | "keywords": [ 9 | "egg", 10 | "egg-plugin", 11 | "sequelize", 12 | "leoric", 13 | "orm", 14 | "model" 15 | ], 16 | "main": "index.js", 17 | "types": "types/index.d.ts", 18 | "files": [ 19 | "config", 20 | "lib", 21 | "types", 22 | "app.js", 23 | "agent.js" 24 | ], 25 | "scripts": { 26 | "pretest": "run-s dts test:prepare", 27 | "test:prepare": "./test/prepare.sh", 28 | "dts": "run-p dts:test dts:example", 29 | "dts:test": "tsc", 30 | "dts:example": "run-p dts:example:basic dts:example:sequelize", 31 | "dts:example:basic": "cd examples/typescript/basic/ && tsc", 32 | "dts:example:sequelize": "cd examples/typescript/sequelize/ && tsc", 33 | "test": "DEBUG=leoric egg-bin test --full-trace", 34 | "test-local": "DEBUG=leoric egg-bin test --full-trace", 35 | "coveralls": "egg-bin cov" 36 | }, 37 | "maintainers": [ 38 | "cyjake (http://cyj.me)", 39 | "jimmydaddy " 40 | ], 41 | "license": "MIT", 42 | "homepage": "https://github.com/eggjs/egg-orm", 43 | "bugs": { 44 | "url": "https://github.com/eggjs/egg/issues" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/eggjs/egg-orm.git" 49 | }, 50 | "dependencies": { 51 | "debug": "^4.3.4", 52 | "leoric": "^2.9.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/eslint-parser": "^7.17.0", 56 | "@eggjs/tegg": "^1.4.0", 57 | "@eggjs/tegg-config": "^1.2.1", 58 | "@eggjs/tegg-plugin": "^1.3.8", 59 | "@types/mocha": "^9.1.0", 60 | "egg": "^2.25.0", 61 | "egg-bin": "^4.14.0", 62 | "egg-mock": "^3.24.2", 63 | "eslint": "^8.12.0", 64 | "eslint-config-egg": "^7.5.1", 65 | "mysql": "^2.17.1", 66 | "mysql2": "^3.9.4", 67 | "npm-run-all": "^4.1.5", 68 | "typescript": "^4.6.2" 69 | }, 70 | "engines": { 71 | "node": ">= 10.0.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/basic.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').strict; 4 | const mm = require('egg-mock'); 5 | const path = require('path'); 6 | 7 | describe('test/basic.test.js', () => { 8 | let app; 9 | 10 | before(() => { 11 | app = mm.app({ 12 | baseDir: path.join(__dirname, '../examples/basic'), 13 | }); 14 | return app.ready(); 15 | }); 16 | 17 | after(mm.restore); 18 | 19 | describe('app.model', function() { 20 | it('should be accessible via app.model', function() { 21 | assert(app.model); 22 | assert(app.model.User); 23 | }); 24 | 25 | it('should be able to access app from model', async function() { 26 | assert(app.model.User.app); 27 | assert((new app.model.User()).app); 28 | }); 29 | }); 30 | 31 | describe('GET /users/:id, POST /users', () => { 32 | beforeEach(async function() { 33 | await app.model.User.truncate(); 34 | }); 35 | 36 | it('should create and get user successfully', async () => { 37 | const res = await app.httpRequest() 38 | .post('/users') 39 | .send({ 40 | nickname: 'jack', 41 | email: 'jack@example.com', 42 | }); 43 | assert(res.status === 200); 44 | assert(res.body.id); 45 | assert(res.body.nickname === 'jack'); 46 | assert(res.body.email === 'jack@example.com'); 47 | assert(res.body.createdAt); 48 | 49 | const res2 = await app.httpRequest() 50 | .get(`/users/${res.body.id}`) 51 | .send({ 52 | nickname: 'jack', 53 | email: 'jack@example.com', 54 | }); 55 | assert(res2.status === 200); 56 | assert.deepEqual(res2.body, res.body); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/delegate.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').strict; 4 | const mm = require('egg-mock'); 5 | const path = require('path'); 6 | 7 | describe('test/delegate.test.js', () => { 8 | let app; 9 | 10 | before(() => { 11 | app = mm.app({ 12 | baseDir: path.join(__dirname, '../examples/delegate'), 13 | }); 14 | return app.ready(); 15 | }); 16 | 17 | after(mm.restore); 18 | 19 | describe('app.orm', () => { 20 | it('should be accessible via app.orm', () => { 21 | assert(app.orm); 22 | assert(app.orm.User); 23 | }); 24 | 25 | it('should be able to access app from model', async function() { 26 | assert(app.orm.User.app); 27 | assert((new app.orm.User()).app); 28 | }); 29 | }); 30 | 31 | describe('ctx.orm', () => { 32 | let ctx; 33 | 34 | beforeEach(() => { 35 | ctx = app.mockContext(); 36 | }); 37 | 38 | it('should be accessible via ctx.orm', () => { 39 | assert(ctx.orm); 40 | assert(ctx.orm.ctx === ctx); 41 | 42 | // access twice to make sure avoiding duplicated injection 43 | const User = ctx.orm.User; 44 | assert(ctx.orm.User === User); 45 | assert(ctx.orm.User === User); 46 | assert(ctx.orm !== app.orm); 47 | const ctxModel = ctx.orm; 48 | assert(ctx.orm === ctxModel); 49 | assert(ctx.orm.User.ctx === ctx); 50 | 51 | const user = ctx.orm.User.build({ 52 | nickname: 'foo nickname', 53 | }); 54 | assert(user.nickname === 'foo nickname'); 55 | 56 | const user2 = new ctx.orm.User({ 57 | nickname: 'bar nickname', 58 | }); 59 | assert(user2.nickname === 'bar nickname'); 60 | }); 61 | 62 | it('should be able to access loaded models', () => { 63 | const { User } = app.orm; 64 | const { User: ContextUser } = ctx.orm; 65 | 66 | assert.ok(User); 67 | assert.ok(User.ctx == null); 68 | // subclass 69 | assert.ok(ContextUser.prototype instanceof User); 70 | assert.equal(ContextUser.ctx, ctx); 71 | }); 72 | 73 | it('should have different models on different contexts', () => { 74 | const ctx2 = app.mockContext(); 75 | assert.notEqual(ctx.orm, ctx2.orm); 76 | assert.notEqual(ctx.orm.User, ctx2.orm.User); 77 | }); 78 | }); 79 | 80 | describe('GET /users/:id, POST /users', () => { 81 | beforeEach(async function() { 82 | await app.orm.User.truncate(); 83 | }); 84 | 85 | it('should create and get user successfully', async () => { 86 | const res = await app.httpRequest() 87 | .post('/users') 88 | .send({ 89 | nickname: 'rose', 90 | email: 'rose@example.com', 91 | }); 92 | assert(res.status === 200); 93 | assert(res.body.id); 94 | assert(res.body.nickname === 'rose'); 95 | assert(res.body.email === 'rose@example.com'); 96 | assert(res.body.createdAt); 97 | 98 | const res2 = await app.httpRequest() 99 | .get(`/users/${res.body.id}`) 100 | .send({ 101 | nickname: 'rose', 102 | email: 'rose@example.com', 103 | }); 104 | assert(res2.status === 200); 105 | assert.deepEqual(res2.body, res.body); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/dumpfile.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `users`; 2 | DROP TABLE IF EXISTS `posts`; 3 | DROP TABLE IF EXISTS `comments`; 4 | 5 | CREATE TABLE `users` ( 6 | `id` bigint(20) AUTO_INCREMENT PRIMARY KEY, 7 | `gmt_create` datetime(6) NOT NULL, 8 | `email` varchar(256) NOT NULL UNIQUE, 9 | `nickname` varchar(256) NOT NULL 10 | ); 11 | 12 | CREATE TABLE `posts` ( 13 | `id` bigint(20) AUTO_INCREMENT PRIMARY KEY, 14 | `gmt_create` datetime(6) NOT NULL, 15 | `content` TEXT, 16 | `description` varchar(256) NOT NULL, 17 | `user_id` bigint(20) 18 | ); 19 | 20 | CREATE TABLE `comments` ( 21 | `id` bigint(20) AUTO_INCREMENT PRIMARY KEY, 22 | `content` TEXT, 23 | `user_id` bigint(20) 24 | ) 25 | -------------------------------------------------------------------------------- /test/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ${GITHUB_ACTIONS:-false} = true ]; then 4 | mysqladmin -h127.0.0.1 -P${MYSQL_PORT:-3306} -uroot -p${MYSQL_ROOT_PASSWORD} password ''; 5 | fi 6 | 7 | ## 8 | # MySQL 9 | cat < { 8 | let app; 9 | 10 | before(() => { 11 | app = mm.app({ 12 | baseDir: path.join(__dirname, '../examples/sequelize'), 13 | }); 14 | return app.ready(); 15 | }); 16 | 17 | after(mm.restore); 18 | 19 | describe('app.model', () => { 20 | it('should be accessible via app.model', () => { 21 | assert(app.model); 22 | assert(app.model.User); 23 | }); 24 | 25 | it('should be able to access app from model', async function() { 26 | assert(app.model.User.app); 27 | assert((new app.model.User()).app); 28 | }); 29 | }); 30 | 31 | describe('ctx.model', () => { 32 | let ctx; 33 | 34 | beforeEach(() => { 35 | ctx = app.mockContext(); 36 | }); 37 | 38 | it('should be accessible via ctx.model by extends', () => { 39 | assert(ctx.model); 40 | // access twice to make sure avoiding duplicated model injection 41 | const User = ctx.model.User; 42 | assert(ctx.model.User === User); 43 | assert(ctx.model.User === User); 44 | assert(ctx.model !== app.model); 45 | const ctxModel = ctx.model; 46 | assert(ctx.model === ctxModel); 47 | assert(ctx.model.User.ctx === ctx); 48 | assert((new ctx.model.User()).ctx === ctx); 49 | assert(ctx.model.User.app); 50 | assert((new ctx.model.User()).app); 51 | assert(ctx.model.User.name === 'User'); 52 | 53 | const user = ctx.model.User.build({ 54 | nickname: 'foo nickname', 55 | }); 56 | assert(user.nickname === 'foo nickname'); 57 | 58 | const user2 = new ctx.model.User({ 59 | nickname: 'bar nickname', 60 | }); 61 | assert(user2.nickname === 'bar nickname'); 62 | }); 63 | 64 | it('should be able to access loaded models by extends', () => { 65 | const { User } = app.model; 66 | const { User: ContextUser } = ctx.model; 67 | 68 | assert.ok(User); 69 | assert.ok(User.ctx == null); 70 | // subclass 71 | assert.ok(ContextUser.prototype instanceof User); 72 | assert.equal(ContextUser.ctx, ctx); 73 | }); 74 | 75 | it('should be accessible via ctx.model by define', () => { 76 | assert(ctx.model); 77 | // access twice to make sure avoiding duplicated model injection 78 | const Post = ctx.model.Post; 79 | assert(ctx.model.Post === Post); 80 | assert(ctx.model.Post === Post); 81 | assert(ctx.model !== app.model); 82 | const ctxModel = ctx.model; 83 | assert(ctx.model === ctxModel); 84 | assert(ctx.model.Post.ctx === ctx); 85 | 86 | const post = ctx.model.Post.build({ 87 | description: 'foo nickname', 88 | }); 89 | assert(post.description === 'foo nickname'); 90 | 91 | const post2 = new ctx.model.Post({ 92 | description: 'bar nickname', 93 | }); 94 | assert(post2.description === 'bar nickname'); 95 | }); 96 | 97 | it('should be able to access loaded models by define', () => { 98 | const { Post } = app.model; 99 | const { Post: ContextPost } = ctx.model; 100 | 101 | assert.ok(Post); 102 | assert.ok(Post.ctx == null); 103 | // subclass 104 | assert.ok(ContextPost.prototype instanceof Post); 105 | assert.equal(ContextPost.ctx, ctx); 106 | }); 107 | 108 | it('should have different models on different contexts', () => { 109 | const ctx2 = app.mockContext(); 110 | assert.notEqual(ctx.model, ctx2.model); 111 | assert.notEqual(ctx.model.User, ctx2.model.User); 112 | }); 113 | }); 114 | 115 | describe('sequelize', () => { 116 | it('should extend Bone with sequelize methods', () => { 117 | assert.equal(typeof app.model.User.findAll, 'function'); 118 | }); 119 | 120 | it('should be able to handle multiple inheritance', async () => { 121 | assert.equal(app.model.Comment.shardingKey, 'userId'); 122 | assert.equal(typeof app.model.Comment.findAll, 'function'); 123 | }); 124 | }); 125 | 126 | describe('GET /users/:id, POST /users', () => { 127 | beforeEach(async function() { 128 | await app.model.User.truncate(); 129 | }); 130 | 131 | it('should create and get user successfully', async () => { 132 | const res = await app.httpRequest() 133 | .post('/users') 134 | .send({ 135 | nickname: 'jack', 136 | email: 'jack@example.com', 137 | }); 138 | assert(res.status === 200); 139 | assert(res.body.id); 140 | assert(res.body.nickname === 'jack'); 141 | assert(res.body.email === 'jack@example.com'); 142 | assert(res.body.createdAt); 143 | // should ignore properties injected by egg loader 144 | assert(res.body.fullPath == null); 145 | assert(res.body.pathName == null); 146 | 147 | // should not interfere JSON dump 148 | assert(res.body.hasOwnProperty('ctx') === false); 149 | assert(res.body.hasOwnProperty('app') === false); 150 | 151 | const res2 = await app.httpRequest() 152 | .get(`/users/${res.body.id}`) 153 | .send({ 154 | nickname: 'jack', 155 | email: 'jack@example.com', 156 | }); 157 | assert(res2.status === 200); 158 | assert.deepEqual(res2.body, res.body); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/typescript/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert'; 2 | import mm from 'egg-mock'; 3 | import path from 'path'; 4 | 5 | describe('test/typesript/basic/plugin.test.ts', () => { 6 | let app; 7 | 8 | before(() => { 9 | mm.restore(); 10 | app = mm.app({ 11 | baseDir: path.join(__dirname, '../../examples/typescript/basic'), 12 | }); 13 | return app.ready(); 14 | }); 15 | 16 | after(mm.restore); 17 | 18 | describe('app.model', () => { 19 | it('should be accessible via app.model', () => { 20 | assert(app.model); 21 | assert(app.model.User); 22 | }); 23 | 24 | it('should be able to access app from model', async function() { 25 | assert(app.model.User.app); 26 | assert((new app.model.User()).app); 27 | }); 28 | }); 29 | 30 | describe('models', () => { 31 | it('should be accessible via app.model[name]', () => { 32 | assert.ok(app.model.User.models); 33 | assert.ok(app.model.User.models.User); 34 | assert.ok(app.model.User.models.Post); 35 | }); 36 | 37 | it('should be accessible via ctx.model[name]', () => { 38 | const ctx = app.mockContext(); 39 | assert.ok(ctx.model.User.models); 40 | assert.ok(ctx.model.User.models.User); 41 | assert.ok(ctx.model.User.models.Post); 42 | }); 43 | }); 44 | 45 | describe('ctx.model', () => { 46 | let ctx; 47 | 48 | beforeEach(() => { 49 | ctx = app.mockContext(); 50 | }); 51 | 52 | it('should be accessible via ctx.model by extends', async () => { 53 | assert(ctx.model); 54 | // access twice to make sure avoiding duplicated model injection 55 | const User = ctx.model.User; 56 | assert(ctx.model.User === User); 57 | assert(ctx.model.User === User); 58 | assert(ctx.model !== app.model); 59 | const ctxModel = ctx.model; 60 | assert(ctx.model === ctxModel); 61 | assert(ctx.model.User.ctx === ctx); 62 | assert((new ctx.model.User()).ctx === ctx); 63 | assert(ctx.model.User.app); 64 | assert((new ctx.model.User()).app); 65 | assert(ctx.model.User.name === 'User'); 66 | 67 | const user = new ctx.model.User({ 68 | nickname: 'foo nickname', 69 | email: 'foo@bar.com', 70 | }); 71 | assert.ok(!ctx.model.User.attributes['content']); 72 | assert(user.nickname === 'foo nickname'); 73 | await user.save(); 74 | 75 | const user2 = new ctx.model.User({ 76 | nickname: 'bar nickname', 77 | }); 78 | assert(user2.nickname === 'bar nickname'); 79 | }); 80 | 81 | it('should be able to access loaded models by extends', () => { 82 | const { User } = app.model; 83 | const { User: ContextUser } = ctx.model; 84 | 85 | assert.ok(User); 86 | assert.ok(User.ctx == null); 87 | // subclass 88 | assert.ok(ContextUser.prototype instanceof User); 89 | assert.equal(ContextUser.ctx, ctx); 90 | 91 | const { Post } = app.model; 92 | const p = new Post(); 93 | assert.equal(p.description, 'defaultDesc'); 94 | const p1 = new ctx.model.User.models.Post(); 95 | assert.equal(p1.description, 'defaultDesc'); 96 | }); 97 | 98 | it('should be accessible via ctx.model by define', async () => { 99 | assert(ctx.model); 100 | // access twice to make sure avoiding duplicated model injection 101 | const Post = ctx.model.Post; 102 | assert(ctx.model.Post === Post); 103 | assert(ctx.model !== app.model); 104 | const ctxModel = ctx.model; 105 | assert(ctx.model === ctxModel); 106 | assert(ctx.model.Post.ctx === ctx); 107 | assert.ok(ctx.model.Post.app); 108 | assert.ok(ctx.model.Post.app === app); 109 | 110 | const post = new ctx.model.Post({ 111 | description: 'foo nickname', 112 | email: 'foo@bar.com', 113 | }); 114 | assert(post.description === 'foo nickname'); 115 | assert.ok(!Post.attributes['nickname']); 116 | await post.save(); 117 | assert.ok(post.id); 118 | 119 | const post2 = new ctx.model.Post({ 120 | description: 'bar nickname', 121 | }); 122 | assert(post2.description === 'bar nickname'); 123 | }); 124 | 125 | it('should be able to access loaded models by define', () => { 126 | const { Post } = app.model; 127 | const { Post: ContextPost } = ctx.model; 128 | 129 | assert.ok(Post); 130 | assert.ok(Post.ctx == null); 131 | // subclass 132 | assert.ok(ContextPost.prototype instanceof Post); 133 | assert.equal(ContextPost.ctx, ctx); 134 | }); 135 | 136 | it('should have different models on different contexts', () => { 137 | const ctx2 = app.mockContext(); 138 | assert.notEqual(ctx.model, ctx2.model); 139 | assert.notEqual(ctx.model.User, ctx2.model.User); 140 | }); 141 | }); 142 | 143 | describe('GET /users/:id, POST /users', () => { 144 | beforeEach(async function() { 145 | await app.model.User.truncate(); 146 | }); 147 | 148 | it('should create and get user successfully', async () => { 149 | const res = await app.httpRequest() 150 | .post('/users') 151 | .send({ 152 | nickname: 'jack', 153 | email: 'jack@example.com', 154 | }); 155 | assert(res.status === 200); 156 | assert(res.body.id); 157 | assert(res.body.nickname === 'jack'); 158 | assert(res.body.email === 'jack@example.com'); 159 | assert(res.body.createdAt); 160 | // should ignore properties injected by egg loader 161 | assert(res.body.fullPath == null); 162 | assert(res.body.pathName == null); 163 | 164 | // should not interfere JSON dump 165 | assert(res.body.hasOwnProperty('ctx') === false); 166 | assert(res.body.hasOwnProperty('app') === false); 167 | 168 | const res2 = await app.httpRequest() 169 | .get(`/users/${res.body.id}`) 170 | .send({ 171 | nickname: 'jack', 172 | email: 'jack@example.com', 173 | }); 174 | assert(res2.status === 200); 175 | assert.deepEqual(res2.body, res.body); 176 | }); 177 | }); 178 | 179 | describe('tegg-module-mixing', () => { 180 | it('should work', async () => { 181 | const ctx = await app.mockModuleContext(); 182 | 183 | const user = await ctx.model.User.create({ 184 | nickname: 'jerry', 185 | email: 'jerry@example.com', 186 | }); 187 | 188 | assert.ok(user); 189 | 190 | const res = await ctx.module.testModule.helloService.sayHello(user.id); 191 | 192 | assert.equal(res, 'hello jerry'); 193 | }); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/typescript/sequelize.test.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'assert'; 2 | import mm from 'egg-mock'; 3 | import path from 'path'; 4 | 5 | describe('test/typescript/sequelize/plugin.test.ts', () => { 6 | let app; 7 | 8 | before(() => { 9 | app = mm.app({ 10 | baseDir: path.join(__dirname, '../../examples/typescript/sequelize'), 11 | }); 12 | return app.ready(); 13 | }); 14 | 15 | after(mm.restore); 16 | 17 | describe('app.model', () => { 18 | it('should be accessible via app.model', () => { 19 | assert(app.model); 20 | assert(app.model.User); 21 | }); 22 | 23 | it('should be able to access app from model', async function() { 24 | assert(app.model.User.app); 25 | assert((new app.model.User()).app); 26 | }); 27 | }); 28 | 29 | describe('models', () => { 30 | it('should be accessible via app.model[name]', () => { 31 | assert.ok(app.model.User.models); 32 | assert.ok(app.model.User.models.User); 33 | assert.ok(app.model.User.models.Post); 34 | }); 35 | 36 | it('should be accessible via ctx.model[name]', () => { 37 | const ctx = app.mockContext(); 38 | assert.ok(ctx.model.User.models); 39 | assert.ok(ctx.model.User.models.User); 40 | assert.ok(ctx.model.User.models.Post); 41 | }); 42 | }); 43 | 44 | describe('sequelize', () => { 45 | it('should extend Bone with sequelize methods', () => { 46 | assert.equal(typeof app.model.User.findAll, 'function'); 47 | }); 48 | 49 | it('should be able to handle multiple inheritance', async () => { 50 | assert.equal(app.model.Comment.shardingKey, 'userId'); 51 | assert.equal(typeof app.model.Comment.findAll, 'function'); 52 | }); 53 | }); 54 | 55 | describe('ctx.model', () => { 56 | let ctx; 57 | 58 | beforeEach(() => { 59 | ctx = app.mockContext(); 60 | }); 61 | 62 | it('should be accessible via ctx.model by extends', async () => { 63 | assert(ctx.model); 64 | // access twice to make sure avoiding duplicated model injection 65 | const User = ctx.model.User; 66 | assert(ctx.model.User === User); 67 | assert(ctx.model !== app.model); 68 | const ctxModel = ctx.model; 69 | assert(ctx.model === ctxModel); 70 | assert(ctx.model.User.ctx === ctx); 71 | assert.ok(ctx.model.User.app); 72 | assert.ok(ctx.model.User.app === app); 73 | assert((new ctx.model.User()).ctx === ctx); 74 | assert(ctx.model.User.app); 75 | assert.ok((new ctx.model.User()).app); 76 | assert.ok((new ctx.model.User()).app === app); 77 | assert(ctx.model.User.name === 'User'); 78 | 79 | const user = ctx.model.User.build({ 80 | nickname: 'foo nickname', 81 | email: 'foo@bar.com', 82 | }); 83 | assert(user.nickname === 'foo nickname'); 84 | assert.ok(!ctx.model.User.attributes['content']); 85 | await user.save(); 86 | assert.ok(user.id); 87 | 88 | const user2 = new ctx.model.User({ 89 | nickname: 'bar nickname', 90 | }); 91 | assert(user2.nickname === 'bar nickname'); 92 | }); 93 | 94 | it('should be able to access loaded models by extends', async () => { 95 | const { User } = app.model; 96 | const { User: ContextUser } = ctx.model; 97 | 98 | assert.ok(User); 99 | assert.ok(User.ctx == null); 100 | // subclass 101 | assert.ok(ContextUser.prototype instanceof User); 102 | assert.equal(ContextUser.ctx, ctx); 103 | 104 | const { Post } = app.model; 105 | const p = new Post({ user_id: 1, nickname: 'yexy' }); 106 | assert.equal(p.description, 'defaultDesc'); 107 | assert.ok(!Post.attributes['nickname']); 108 | await p.save(); 109 | assert.ok(p.id); 110 | assert.equal(p.user_id, 1); 111 | const p1 = new ctx.model.User.models.Post(); 112 | assert.equal(p1.description, 'defaultDesc'); 113 | }); 114 | 115 | it('should be accessible via ctx.model by define', () => { 116 | assert(ctx.model); 117 | // access twice to make sure avoiding duplicated model injection 118 | const Post = ctx.model.Post; 119 | assert(ctx.model.Post === Post); 120 | assert(ctx.model !== app.model); 121 | const ctxModel = ctx.model; 122 | assert(ctx.model === ctxModel); 123 | assert(ctx.model.Post.ctx === ctx); 124 | 125 | const post = ctx.model.Post.build({ 126 | description: 'foo nickname', 127 | }); 128 | assert(post.description === 'foo nickname'); 129 | 130 | const post2 = new ctx.model.Post({ 131 | description: 'bar nickname', 132 | }); 133 | assert(post2.description === 'bar nickname'); 134 | }); 135 | 136 | it('should be able to access loaded models by define', () => { 137 | const { Post } = app.model; 138 | const { Post: ContextPost } = ctx.model; 139 | 140 | assert.ok(Post); 141 | assert.ok(Post.ctx == null); 142 | // subclass 143 | assert.ok(ContextPost.prototype instanceof Post); 144 | assert.equal(ContextPost.ctx, ctx); 145 | }); 146 | 147 | it('should have different models on different contexts', () => { 148 | const ctx2 = app.mockContext(); 149 | assert.notEqual(ctx.model, ctx2.model); 150 | assert.notEqual(ctx.model.User, ctx2.model.User); 151 | }); 152 | }); 153 | 154 | describe('GET /users/:id, POST /users', () => { 155 | beforeEach(async function() { 156 | await app.model.User.truncate(); 157 | }); 158 | 159 | it('should create and get user successfully', async () => { 160 | const res = await app.httpRequest() 161 | .post('/users') 162 | .send({ 163 | nickname: 'jack', 164 | email: 'jack@example.com', 165 | }); 166 | assert(res.status === 200); 167 | assert(res.body.id); 168 | assert(res.body.nickname === 'jack'); 169 | assert(res.body.email === 'jack@example.com'); 170 | assert.equal(res.body.userAppName, `${app.name}jack`); 171 | assert(res.body.created_at); 172 | // should ignore properties injected by egg loader 173 | assert(res.body.fullPath == null); 174 | assert(res.body.pathName == null); 175 | 176 | // should not interfere JSON dump 177 | assert(res.body.hasOwnProperty('ctx') === false); 178 | assert(res.body.hasOwnProperty('app') === false); 179 | 180 | const res2 = await app.httpRequest() 181 | .get(`/users/${res.body.id}`) 182 | .send({ 183 | nickname: 'jack', 184 | email: 'jack@example.com', 185 | }); 186 | assert(res2.status === 200); 187 | assert.deepEqual(res2.body, res.body); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "moduleResolution": "Node", 5 | "module": "CommonJS", 6 | "experimentalDecorators": true, 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": [ 13 | "test/typescript", 14 | "types" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Context, Application } from 'egg'; 2 | 3 | import Realm, { 4 | Bone, 5 | DataTypes, 6 | Column, HasMany, HasOne, BelongsTo, 7 | ConnectOptions, SequelizeBone, 8 | } from 'leoric'; 9 | 10 | export * from 'leoric'; 11 | 12 | declare class MySequelizeBone extends SequelizeBone { 13 | static ctx: Context; 14 | static app: Application; 15 | 16 | ctx: Context; 17 | app: Application; 18 | 19 | } 20 | 21 | declare class MyBone extends Bone { 22 | static ctx: Context; 23 | static app: Application; 24 | 25 | ctx: Context; 26 | app: Application; 27 | 28 | } 29 | 30 | export { MySequelizeBone as SequelizeBone, MyBone as Bone } 31 | 32 | interface EggOrmOptions extends ConnectOptions { 33 | delegate?: string; 34 | migrations?: string; 35 | baseDir?: string; 36 | define?: { 37 | underscored?: boolean; 38 | } 39 | } 40 | 41 | interface DataSources { 42 | datasources: EggOrmOptions[]; 43 | } 44 | 45 | declare module 'egg' { 46 | class IModel extends Realm { 47 | Column: typeof Column; 48 | HasMany: typeof HasMany; 49 | HasOne: typeof HasOne; 50 | BelongsTo: typeof BelongsTo; 51 | DataTypes: typeof DataTypes; 52 | Model: typeof Bone | typeof SequelizeBone; 53 | } 54 | 55 | // extend app 56 | interface Application { 57 | model: IModel; 58 | } 59 | 60 | // extend context 61 | interface Context { 62 | model: IModel; 63 | } 64 | 65 | // extend your config 66 | interface EggAppConfig { 67 | orm: EggOrmOptions | DataSources; 68 | } 69 | } 70 | --------------------------------------------------------------------------------