feat: improve code...
This commit is contained in:
parent
4651d3dddd
commit
60bbbb5f35
@ -23,7 +23,7 @@ const app = new Application({
|
|||||||
// 配置一张 users 表
|
// 配置一张 users 表
|
||||||
app.collection({
|
app.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'username' },
|
{ type: 'string', name: 'username' },
|
||||||
{ type: 'password', name: 'password' }
|
{ type: 'password', name: 'password' }
|
||||||
],
|
],
|
||||||
@ -114,7 +114,6 @@ NocoBase 的 Application 继承了 Koa,集成了 DB 和 CLI,添加了一些
|
|||||||
|
|
||||||
- `app.db`:数据库实例,每个 app 都有自己的 db。
|
- `app.db`:数据库实例,每个 app 都有自己的 db。
|
||||||
- `db.getCollection()` 数据表/数据集
|
- `db.getCollection()` 数据表/数据集
|
||||||
- `collection.schema` 数据结构
|
|
||||||
- `collection.repository` 数据仓库
|
- `collection.repository` 数据仓库
|
||||||
- `collection.model` 数据模型
|
- `collection.model` 数据模型
|
||||||
- `db.on()` 添加事件监听,由 EventEmitter 提供
|
- `db.on()` 添加事件监听,由 EventEmitter 提供
|
||||||
@ -175,7 +174,7 @@ NocoBase 通过 `app.collection()` 方法定义数据的 Schema,Schema 的类
|
|||||||
// 用户
|
// 用户
|
||||||
app.collection({
|
app.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: {
|
fields: {
|
||||||
username: { type: 'string', unique: true },
|
username: { type: 'string', unique: true },
|
||||||
password: { type: 'password', unique: true },
|
password: { type: 'password', unique: true },
|
||||||
posts: { type: 'hasMany' },
|
posts: { type: 'hasMany' },
|
||||||
@ -185,7 +184,7 @@ app.collection({
|
|||||||
// 文章
|
// 文章
|
||||||
app.collection({
|
app.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: {
|
fields: {
|
||||||
title: 'string',
|
title: 'string',
|
||||||
content: 'text',
|
content: 'text',
|
||||||
tags: 'belongsToMany',
|
tags: 'belongsToMany',
|
||||||
@ -197,7 +196,7 @@ app.collection({
|
|||||||
// 标签
|
// 标签
|
||||||
app.collection({
|
app.collection({
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
{ type: 'belongsToMany', name: 'posts' },
|
{ type: 'belongsToMany', name: 'posts' },
|
||||||
],
|
],
|
||||||
@ -206,7 +205,7 @@ app.collection({
|
|||||||
// 评论
|
// 评论
|
||||||
app.collection({
|
app.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'text', name: 'content' },
|
{ type: 'text', name: 'content' },
|
||||||
{ type: 'belongsTo', name: 'user' },
|
{ type: 'belongsTo', name: 'user' },
|
||||||
],
|
],
|
||||||
@ -215,43 +214,52 @@ app.collection({
|
|||||||
|
|
||||||
除了通过 `app.collection()` 配置 schema,也可以直接调用 api 插入或修改 schema,collection 的核心 API 有:
|
除了通过 `app.collection()` 配置 schema,也可以直接调用 api 插入或修改 schema,collection 的核心 API 有:
|
||||||
|
|
||||||
- `collection.schema` 当前 collection 的数据结构
|
- `collection` 当前 collection 的数据结构
|
||||||
- `schema.has()` 判断是否存在
|
- `collection.hasField()` 判断字段是否存在
|
||||||
- `schema.get()` 获取
|
- `collection.addField()` 添加字段配置
|
||||||
- `schema.set()` 添加或更新
|
- `collection.getField()` 获取字段配置
|
||||||
- `schema.merge()` 添加、或指定 key path 替换
|
- `collection.removeField()` 移除字段配置
|
||||||
- `schema.replace()` 替换
|
- `collection.sync()` 与数据库表结构同步
|
||||||
- `schema.delete()` 删除
|
|
||||||
- `collection.repository` 当前 collection 的数据仓库
|
- `collection.repository` 当前 collection 的数据仓库
|
||||||
- `repository.findAll()`
|
- `repository.findMany()`
|
||||||
- `repository.findOne()`
|
- `repository.findOne()`
|
||||||
- `repository.create()`
|
- `repository.create()`
|
||||||
- `repository.update()`
|
- `repository.update()`
|
||||||
- `repository.destroy()`
|
- `repository.destroy()`
|
||||||
|
- `repository.relatedQuery().for()`
|
||||||
|
- `create()`
|
||||||
|
- `update()`
|
||||||
|
- `destroy()`
|
||||||
|
- `findMany()`
|
||||||
|
- `findOne()`
|
||||||
|
- `set()`
|
||||||
|
- `add()`
|
||||||
|
- `remove()`
|
||||||
|
- `toggle()`
|
||||||
- `collection.model` 当前 collection 的数据模型
|
- `collection.model` 当前 collection 的数据模型
|
||||||
|
|
||||||
Schema 示例:
|
Collection 示例:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const collection = app.db.getCollection('posts');
|
const collection = app.db.getCollection('posts');
|
||||||
|
|
||||||
collection.schema.has('title');
|
collection.hasField('title');
|
||||||
|
|
||||||
collection.schema.get('title');
|
collection.getField('title');
|
||||||
|
|
||||||
// 添加或更新
|
// 添加或更新
|
||||||
collection.schema.set('content', {
|
collection.addField({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
name: 'content',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 移除
|
// 移除
|
||||||
collection.schema.delete('content');
|
collection.removeField('content');
|
||||||
|
|
||||||
// 添加、或指定 key path 替换
|
// 添加、或指定 key path 替换
|
||||||
collection.schema.merge({
|
collection.mergeField({
|
||||||
content: {
|
name: 'content',
|
||||||
type: 'content',
|
type: 'string',
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
除了全局的 `db.sync()`,也有 `collection.sync()` 方法。
|
除了全局的 `db.sync()`,也有 `collection.sync()` 方法。
|
||||||
@ -268,9 +276,9 @@ await collection.sync();
|
|||||||
通过 Repository 创建数据
|
通过 Repository 创建数据
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const repository = app.db.getRepository('users');
|
const User = app.db.getCollection('users');
|
||||||
|
|
||||||
const user = await repository.create({
|
const user = await User.repository.create({
|
||||||
title: 't1',
|
title: 't1',
|
||||||
content: 'c1',
|
content: 'c1',
|
||||||
author: 1,
|
author: 1,
|
||||||
@ -280,7 +288,7 @@ const user = await repository.create({
|
|||||||
blacklist: [],
|
blacklist: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
await repository.findAll({
|
await User.repository.findMany({
|
||||||
filter: {
|
filter: {
|
||||||
title: 't1',
|
title: 't1',
|
||||||
},
|
},
|
||||||
@ -290,7 +298,7 @@ await repository.findAll({
|
|||||||
perPage: 20,
|
perPage: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
await repository.findOne({
|
await User.repository.findOne({
|
||||||
filter: {
|
filter: {
|
||||||
title: 't1',
|
title: 't1',
|
||||||
},
|
},
|
||||||
@ -300,7 +308,7 @@ await repository.findOne({
|
|||||||
perPage: 20,
|
perPage: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
await repository.update({
|
await User.repository.update({
|
||||||
title: 't1',
|
title: 't1',
|
||||||
content: 'c1',
|
content: 'c1',
|
||||||
author: 1,
|
author: 1,
|
||||||
@ -311,7 +319,7 @@ await repository.update({
|
|||||||
blacklist: [],
|
blacklist: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
await repository.destroy({
|
await User.repository.destroy({
|
||||||
filter: {},
|
filter: {},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@ -319,15 +327,11 @@ await repository.destroy({
|
|||||||
通过 Model 创建数据
|
通过 Model 创建数据
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const User = db.getModel('users');
|
const User = db.getCollection('users');
|
||||||
const user = await User.create({
|
const user = await User.model.create({
|
||||||
title: 't1',
|
title: 't1',
|
||||||
content: 'c1',
|
content: 'c1',
|
||||||
});
|
});
|
||||||
await user.updateAssociations({
|
|
||||||
author: 1,
|
|
||||||
tags: [1,2,3],
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 资源 & 操作 - Resource & Action
|
## 资源 & 操作 - Resource & Action
|
||||||
|
@ -15,7 +15,7 @@ describe('belongs to field', () => {
|
|||||||
it('association undefined', async () => {
|
it('association undefined', async () => {
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'belongsTo', name: 'post' }],
|
fields: [{ type: 'belongsTo', name: 'post' }],
|
||||||
});
|
});
|
||||||
expect(Comment.model.associations['post']).toBeUndefined();
|
expect(Comment.model.associations['post']).toBeUndefined();
|
||||||
});
|
});
|
||||||
@ -23,7 +23,7 @@ describe('belongs to field', () => {
|
|||||||
it('association defined', async () => {
|
it('association defined', async () => {
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'content' },
|
{ type: 'string', name: 'content' },
|
||||||
{ type: 'belongsTo', name: 'post' },
|
{ type: 'belongsTo', name: 'post' },
|
||||||
],
|
],
|
||||||
@ -31,7 +31,7 @@ describe('belongs to field', () => {
|
|||||||
expect(Comment.model.associations.post).toBeUndefined();
|
expect(Comment.model.associations.post).toBeUndefined();
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'title' },
|
{ type: 'string', name: 'title' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -63,13 +63,13 @@ describe('belongs to field', () => {
|
|||||||
it('custom targetKey and foreignKey', async () => {
|
it('custom targetKey and foreignKey', async () => {
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'key', unique: true },
|
{ type: 'string', name: 'key', unique: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: 'belongsTo',
|
type: 'belongsTo',
|
||||||
name: 'post',
|
name: 'post',
|
||||||
@ -89,7 +89,7 @@ describe('belongs to field', () => {
|
|||||||
it('custom name and target', async () => {
|
it('custom name and target', async () => {
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'content' },
|
{ type: 'string', name: 'content' },
|
||||||
{
|
{
|
||||||
type: 'belongsTo',
|
type: 'belongsTo',
|
||||||
@ -103,7 +103,7 @@ describe('belongs to field', () => {
|
|||||||
expect(Comment.model.associations.article).toBeUndefined();
|
expect(Comment.model.associations.article).toBeUndefined();
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'key', unique: true },
|
{ type: 'string', name: 'key', unique: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -135,17 +135,17 @@ describe('belongs to field', () => {
|
|||||||
it('schema delete', async () => {
|
it('schema delete', async () => {
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'belongsTo', name: 'post' }],
|
fields: [{ type: 'belongsTo', name: 'post' }],
|
||||||
});
|
});
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [{ type: 'hasMany', name: 'comments' }],
|
fields: [{ type: 'hasMany', name: 'comments' }],
|
||||||
});
|
});
|
||||||
// await db.sync();
|
// await db.sync();
|
||||||
Comment.schema.delete('post');
|
Comment.removeField('post');
|
||||||
expect(Comment.model.associations.post).toBeUndefined();
|
expect(Comment.model.associations.post).toBeUndefined();
|
||||||
expect(Comment.model.rawAttributes.postId).toBeDefined();
|
expect(Comment.model.rawAttributes.postId).toBeDefined();
|
||||||
Post.schema.delete('comments');
|
Post.removeField('comments');
|
||||||
expect(Comment.model.rawAttributes.postId).toBeUndefined();
|
expect(Comment.model.rawAttributes.postId).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -15,7 +15,7 @@ describe('belongs to many field', () => {
|
|||||||
it('association undefined', async () => {
|
it('association undefined', async () => {
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
{ type: 'belongsToMany', name: 'tags' },
|
{ type: 'belongsToMany', name: 'tags' },
|
||||||
],
|
],
|
||||||
@ -24,7 +24,7 @@ describe('belongs to many field', () => {
|
|||||||
expect(db.getCollection('posts_tags')).toBeUndefined();
|
expect(db.getCollection('posts_tags')).toBeUndefined();
|
||||||
const Tag = db.collection({
|
const Tag = db.collection({
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
],
|
],
|
||||||
});
|
});
|
@ -15,7 +15,7 @@ describe('has many field', () => {
|
|||||||
it('association undefined', async () => {
|
it('association undefined', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [{ type: 'hasMany', name: 'comments' }],
|
fields: [{ type: 'hasMany', name: 'comments' }],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
expect(collection.model.associations['comments']).toBeUndefined();
|
expect(collection.model.associations['comments']).toBeUndefined();
|
||||||
@ -24,12 +24,12 @@ describe('has many field', () => {
|
|||||||
it('association defined', async () => {
|
it('association defined', async () => {
|
||||||
const { model } = db.collection({
|
const { model } = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [{ type: 'hasMany', name: 'comments' }],
|
fields: [{ type: 'hasMany', name: 'comments' }],
|
||||||
});
|
});
|
||||||
expect(model.associations['comments']).toBeUndefined();
|
expect(model.associations['comments']).toBeUndefined();
|
||||||
const comments = db.collection({
|
const comments = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'string', name: 'content' }],
|
fields: [{ type: 'string', name: 'content' }],
|
||||||
});
|
});
|
||||||
const association = model.associations.comments;
|
const association = model.associations.comments;
|
||||||
expect(association).toBeDefined();
|
expect(association).toBeDefined();
|
||||||
@ -48,10 +48,10 @@ describe('has many field', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('custom sourceKey', async () => {
|
it('custom sourceKey', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'key', unique: true },
|
{ type: 'string', name: 'key', unique: true },
|
||||||
{
|
{
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
@ -63,7 +63,7 @@ describe('has many field', () => {
|
|||||||
});
|
});
|
||||||
const comments = db.collection({
|
const comments = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [],
|
fields: [],
|
||||||
});
|
});
|
||||||
const association = collection.model.associations.comments;
|
const association = collection.model.associations.comments;
|
||||||
expect(association).toBeDefined();
|
expect(association).toBeDefined();
|
||||||
@ -77,7 +77,7 @@ describe('has many field', () => {
|
|||||||
it('custom sourceKey and foreignKey', async () => {
|
it('custom sourceKey and foreignKey', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'key', unique: true },
|
{ type: 'string', name: 'key', unique: true },
|
||||||
{
|
{
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
@ -89,7 +89,7 @@ describe('has many field', () => {
|
|||||||
});
|
});
|
||||||
const comments = db.collection({
|
const comments = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [],
|
fields: [],
|
||||||
});
|
});
|
||||||
const association = collection.model.associations.comments;
|
const association = collection.model.associations.comments;
|
||||||
expect(association).toBeDefined();
|
expect(association).toBeDefined();
|
||||||
@ -103,7 +103,7 @@ describe('has many field', () => {
|
|||||||
it('custom name and target', async () => {
|
it('custom name and target', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'key', unique: true },
|
{ type: 'string', name: 'key', unique: true },
|
||||||
{
|
{
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
@ -116,7 +116,7 @@ describe('has many field', () => {
|
|||||||
});
|
});
|
||||||
db.collection({
|
db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'string', name: 'content' }],
|
fields: [{ type: 'string', name: 'content' }],
|
||||||
});
|
});
|
||||||
const association = collection.model.associations.reviews;
|
const association = collection.model.associations.reviews;
|
||||||
expect(association).toBeDefined();
|
expect(association).toBeDefined();
|
||||||
@ -139,17 +139,17 @@ describe('has many field', () => {
|
|||||||
it('schema delete', async () => {
|
it('schema delete', async () => {
|
||||||
const Post = db.collection({
|
const Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [{ type: 'hasMany', name: 'comments' }],
|
fields: [{ type: 'hasMany', name: 'comments' }],
|
||||||
});
|
});
|
||||||
const Comment = db.collection({
|
const Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'belongsTo', name: 'post' }],
|
fields: [{ type: 'belongsTo', name: 'post' }],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
Post.schema.delete('comments');
|
Post.removeField('comments');
|
||||||
expect(Post.model.associations.comments).toBeUndefined();
|
expect(Post.model.associations.comments).toBeUndefined();
|
||||||
expect(Comment.model.rawAttributes.postId).toBeDefined();
|
expect(Comment.model.rawAttributes.postId).toBeDefined();
|
||||||
Comment.schema.delete('post');
|
Comment.removeField('post');
|
||||||
expect(Comment.model.rawAttributes.postId).toBeUndefined();
|
expect(Comment.model.rawAttributes.postId).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -15,7 +15,7 @@ describe('has many field', () => {
|
|||||||
it('association undefined', async () => {
|
it('association undefined', async () => {
|
||||||
const User = db.collection({
|
const User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [{ type: 'hasOne', name: 'profile' }],
|
fields: [{ type: 'hasOne', name: 'profile' }],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
expect(User.model.associations.profile).toBeUndefined();
|
expect(User.model.associations.profile).toBeUndefined();
|
||||||
@ -24,12 +24,12 @@ describe('has many field', () => {
|
|||||||
it('association defined', async () => {
|
it('association defined', async () => {
|
||||||
const User = db.collection({
|
const User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [{ type: 'hasOne', name: 'profile' }],
|
fields: [{ type: 'hasOne', name: 'profile' }],
|
||||||
});
|
});
|
||||||
expect(User.model.associations.phone).toBeUndefined();
|
expect(User.model.associations.phone).toBeUndefined();
|
||||||
const Profile = db.collection({
|
const Profile = db.collection({
|
||||||
name: 'profiles',
|
name: 'profiles',
|
||||||
schema: [{ type: 'string', name: 'content' }],
|
fields: [{ type: 'string', name: 'content' }],
|
||||||
});
|
});
|
||||||
const association = User.model.associations.profile;
|
const association = User.model.associations.profile;
|
||||||
expect(association).toBeDefined();
|
expect(association).toBeDefined();
|
||||||
@ -51,17 +51,17 @@ describe('has many field', () => {
|
|||||||
it('schema delete', async () => {
|
it('schema delete', async () => {
|
||||||
const User = db.collection({
|
const User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [{ type: 'hasOne', name: 'profile' }],
|
fields: [{ type: 'hasOne', name: 'profile' }],
|
||||||
});
|
});
|
||||||
const Profile = db.collection({
|
const Profile = db.collection({
|
||||||
name: 'profiles',
|
name: 'profiles',
|
||||||
schema: [{ type: 'belongsTo', name: 'user' }],
|
fields: [{ type: 'belongsTo', name: 'user' }],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
User.schema.delete('profile');
|
User.removeField('profile');
|
||||||
expect(User.model.associations.profile).toBeUndefined();
|
expect(User.model.associations.profile).toBeUndefined();
|
||||||
expect(Profile.model.rawAttributes.userId).toBeDefined();
|
expect(Profile.model.rawAttributes.userId).toBeDefined();
|
||||||
Profile.schema.delete('user');
|
Profile.removeField('user');
|
||||||
expect(Profile.model.rawAttributes.userId).toBeUndefined();
|
expect(Profile.model.rawAttributes.userId).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,13 +1,13 @@
|
|||||||
import { Database } from '../../database';
|
import { Database } from '../../database';
|
||||||
import { mockDatabase } from '../';
|
import { mockDatabase } from '../';
|
||||||
import { SortField } from '../../schema-fields';
|
import { SortField } from '../../fields';
|
||||||
|
|
||||||
describe('string field', () => {
|
describe('string field', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
db.registerSchemaTypes({
|
db.registerFieldTypes({
|
||||||
sort: SortField
|
sort: SortField
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -19,7 +19,7 @@ describe('string field', () => {
|
|||||||
it('sort', async () => {
|
it('sort', async () => {
|
||||||
const Test = db.collection({
|
const Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'sort', name: 'sort' },
|
{ type: 'sort', name: 'sort' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -35,7 +35,7 @@ describe('string field', () => {
|
|||||||
it('skip if sort value not empty', async () => {
|
it('skip if sort value not empty', async () => {
|
||||||
const Test = db.collection({
|
const Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'sort', name: 'sort' },
|
{ type: 'sort', name: 'sort' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -51,7 +51,7 @@ describe('string field', () => {
|
|||||||
it('scopeKey', async () => {
|
it('scopeKey', async () => {
|
||||||
const Test = db.collection({
|
const Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'sort', name: 'sort', scopeKey: 'status' },
|
{ type: 'sort', name: 'sort', scopeKey: 'status' },
|
||||||
{ type: 'string', name: 'status' },
|
{ type: 'string', name: 'status' },
|
||||||
],
|
],
|
@ -15,7 +15,7 @@ describe('string field', () => {
|
|||||||
it('define', async () => {
|
it('define', async () => {
|
||||||
const Test = db.collection({
|
const Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -32,12 +32,12 @@ describe('string field', () => {
|
|||||||
it('set', async () => {
|
it('set', async () => {
|
||||||
const Test = db.collection({
|
const Test = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name1' },
|
{ type: 'string', name: 'name1' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
Test.schema.set('name2', { type: 'string' });
|
Test.addField({ type: 'string', name: 'name2' });
|
||||||
await db.sync();
|
await db.sync();
|
||||||
expect(Test.model.rawAttributes['name1']).toBeDefined();
|
expect(Test.model.rawAttributes['name1']).toBeDefined();
|
||||||
expect(Test.model.rawAttributes['name2']).toBeDefined();
|
expect(Test.model.rawAttributes['name2']).toBeDefined();
|
||||||
@ -54,7 +54,7 @@ describe('string field', () => {
|
|||||||
it('model hook', async () => {
|
it('model hook', async () => {
|
||||||
const collection = db.collection({
|
const collection = db.collection({
|
||||||
name: 'tests',
|
name: 'tests',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -65,7 +65,7 @@ describe('string field', () => {
|
|||||||
model.set(name, `${model.get(name)}111`);
|
model.set(name, `${model.get(name)}111`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
collection.schema.set('name2', { type: 'string' });
|
collection.addField({ type: 'string', name: 'name2' });
|
||||||
await db.sync();
|
await db.sync();
|
||||||
const model = await collection.model.create({
|
const model = await collection.model.create({
|
||||||
name: 'n1',
|
name: 'n1',
|
@ -20,7 +20,7 @@ export function getConfig(config = {}, options?: any): DatabaseOptions {
|
|||||||
host: process.env.DB_HOST,
|
host: process.env.DB_HOST,
|
||||||
port: process.env.DB_PORT,
|
port: process.env.DB_PORT,
|
||||||
dialect: process.env.DB_DIALECT,
|
dialect: process.env.DB_DIALECT,
|
||||||
// logging: process.env.DB_LOG_SQL === 'on',
|
logging: process.env.DB_LOG_SQL === 'on',
|
||||||
sync: {
|
sync: {
|
||||||
force: true,
|
force: true,
|
||||||
alter: {
|
alter: {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Database } from '../database';
|
import { Database } from '../database';
|
||||||
import { updateAssociation, updateAssociations } from '../update-associations';
|
|
||||||
import { mockDatabase } from './';
|
import { mockDatabase } from './';
|
||||||
|
|
||||||
describe('repository', () => {
|
describe('repository.find', () => {
|
||||||
let db: Database;
|
let db: Database;
|
||||||
let User: Collection;
|
let User: Collection;
|
||||||
let Post: Collection;
|
let Post: Collection;
|
||||||
@ -13,24 +12,24 @@ describe('repository', () => {
|
|||||||
db = mockDatabase();
|
db = mockDatabase();
|
||||||
User = db.collection({
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
{ type: 'hasMany', name: 'posts' },
|
{ type: 'hasMany', name: 'posts' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
Post = db.collection({
|
Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
{ type: 'hasMany', name: 'comments' },
|
{ type: 'hasMany', name: 'comments' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
Comment = db.collection({
|
Comment = db.collection({
|
||||||
name: 'comments',
|
name: 'comments',
|
||||||
schema: [{ type: 'string', name: 'name' }],
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
await User.repository.bulkCreate([
|
await User.repository.createMany([
|
||||||
{
|
{
|
||||||
name: 'user1',
|
name: 'user1',
|
||||||
posts: [
|
posts: [
|
||||||
@ -128,29 +127,300 @@ describe('repository', () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('findAll', async () => {
|
|
||||||
const data = await User.repository.findAll({
|
|
||||||
filter: {
|
|
||||||
'posts.comments.id': null,
|
|
||||||
},
|
|
||||||
page: 1,
|
|
||||||
pageSize: 1,
|
|
||||||
});
|
|
||||||
console.log(data.count, JSON.stringify(data.rows.map(row => row.toJSON()), null, 2));
|
|
||||||
// expect(data.toJSON()).toMatchObject({
|
|
||||||
// name: 'user3',
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('findOne', async () => {
|
it('findOne', async () => {
|
||||||
const data = await User.repository.findOne({
|
const data = await User.repository.findOne({
|
||||||
filter: {
|
filter: {
|
||||||
'posts.comments.name': 'comment331',
|
'posts.comments.name': 'comment331',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('findMany', async () => {
|
||||||
|
const data = await User.repository.findMany({
|
||||||
|
filter: {
|
||||||
|
'posts.comments.id': null,
|
||||||
|
},
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
data.count,
|
||||||
|
JSON.stringify(
|
||||||
|
data.rows.map((row) => row.toJSON()),
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
// expect(data.toJSON()).toMatchObject({
|
// expect(data.toJSON()).toMatchObject({
|
||||||
// name: 'user3',
|
// name: 'user3',
|
||||||
// });
|
// });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('repository.create', () => {
|
||||||
|
let db: Database;
|
||||||
|
let User: Collection;
|
||||||
|
let Post: Collection;
|
||||||
|
let Comment: Collection;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create', async () => {
|
||||||
|
const user = await User.repository.create({
|
||||||
|
name: 'user1',
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
name: 'post11',
|
||||||
|
comments: [
|
||||||
|
{ name: 'comment111' },
|
||||||
|
{ name: 'comment112' },
|
||||||
|
{ name: 'comment113' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const post = await Post.model.findOne();
|
||||||
|
expect(post).toMatchObject({
|
||||||
|
name: 'post11',
|
||||||
|
userId: user.get('id'),
|
||||||
|
});
|
||||||
|
const comments = await Comment.model.findAll();
|
||||||
|
expect(comments.map((m) => m.get('postId'))).toEqual([
|
||||||
|
post.get('id'),
|
||||||
|
post.get('id'),
|
||||||
|
post.get('id'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('repository.update', () => {
|
||||||
|
let db: Database;
|
||||||
|
let User: Collection;
|
||||||
|
let Post: Collection;
|
||||||
|
let Comment: Collection;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update1', async () => {
|
||||||
|
const user = await User.model.create<any>({
|
||||||
|
name: 'user1',
|
||||||
|
});
|
||||||
|
await User.repository.update(
|
||||||
|
{
|
||||||
|
name: 'user11',
|
||||||
|
posts: [{ name: 'post1' }],
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
const updated = await User.model.findByPk(user.id);
|
||||||
|
expect(updated).toMatchObject({
|
||||||
|
name: 'user11',
|
||||||
|
});
|
||||||
|
const post = await Post.model.findOne({
|
||||||
|
where: {
|
||||||
|
name: 'post1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(post).toMatchObject({
|
||||||
|
name: 'post1',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update2', async () => {
|
||||||
|
const user = await User.model.create<any>({
|
||||||
|
name: 'user1',
|
||||||
|
posts: [{ name: 'post1' }],
|
||||||
|
});
|
||||||
|
await User.repository.update(
|
||||||
|
{
|
||||||
|
name: 'user11',
|
||||||
|
posts: [{ name: 'post1' }],
|
||||||
|
},
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
|
const updated = await User.model.findByPk(user.id);
|
||||||
|
expect(updated).toMatchObject({
|
||||||
|
name: 'user11',
|
||||||
|
});
|
||||||
|
const post = await Post.model.findOne({
|
||||||
|
where: {
|
||||||
|
name: 'post1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(post).toMatchObject({
|
||||||
|
name: 'post1',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('repository.destroy', () => {
|
||||||
|
let db: Database;
|
||||||
|
let User: Collection;
|
||||||
|
let Post: Collection;
|
||||||
|
let Comment: Collection;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroy1', async () => {
|
||||||
|
const user = await User.model.create<any>();
|
||||||
|
await User.repository.destroy(user.id);
|
||||||
|
const user1 = await User.model.findByPk(user.id);
|
||||||
|
expect(user1).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroy2', async () => {
|
||||||
|
const user = await User.model.create<any>();
|
||||||
|
await User.repository.destroy({
|
||||||
|
filter: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const user1 = await User.model.findByPk(user.id);
|
||||||
|
expect(user1).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('repository.relatedQuery', () => {
|
||||||
|
let db: Database;
|
||||||
|
let User: Collection;
|
||||||
|
let Post: Collection;
|
||||||
|
let Comment: Collection;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'belongsTo', name: 'user' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create', async () => {
|
||||||
|
const user = await User.repository.create();
|
||||||
|
const post = await User.repository.relatedQuery('posts').for(user).create({
|
||||||
|
name: 'post1',
|
||||||
|
});
|
||||||
|
expect(post).toMatchObject({
|
||||||
|
name: 'post1',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
const post2 = await User.repository
|
||||||
|
.relatedQuery('posts')
|
||||||
|
.for(user.id)
|
||||||
|
.create({
|
||||||
|
name: 'post2',
|
||||||
|
});
|
||||||
|
expect(post2).toMatchObject({
|
||||||
|
name: 'post2',
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('update', async () => {
|
||||||
|
const post = await Post.repository.create({
|
||||||
|
user: {
|
||||||
|
name: 'user11',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Post.repository.relatedQuery('user').for(post).update({
|
||||||
|
name: 'user12',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,101 +1,294 @@
|
|||||||
|
import { Collection } from '../collection';
|
||||||
import { Database } from '../database';
|
import { Database } from '../database';
|
||||||
import { updateAssociation, updateAssociations } from '../update-associations';
|
import { updateAssociation, updateAssociations } from '../update-associations';
|
||||||
import { mockDatabase } from './';
|
import { mockDatabase } from './';
|
||||||
|
|
||||||
describe('update associations', () => {
|
describe('update associations', () => {
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
describe('belongsTo', () => {
|
||||||
db = mockDatabase();
|
let db: Database;
|
||||||
});
|
beforeEach(() => {
|
||||||
|
db = mockDatabase();
|
||||||
afterEach(async () => {
|
});
|
||||||
await db.close();
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
it('post.user', async () => {
|
||||||
|
const User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [{ type: 'string', name: 'name' }],
|
||||||
|
});
|
||||||
|
const Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'belongsTo', name: 'user' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
const user = await User.model.create<any>({ name: 'user1' });
|
||||||
|
const post1 = await Post.model.create({ name: 'post1' });
|
||||||
|
await updateAssociations(post1, {
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
expect(post1.toJSON()).toMatchObject({
|
||||||
|
id: 1,
|
||||||
|
name: 'post1',
|
||||||
|
userId: 1,
|
||||||
|
user: {
|
||||||
|
id: 1,
|
||||||
|
name: 'user1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post2 = await Post.model.create({ name: 'post2' });
|
||||||
|
await updateAssociations(post2, {
|
||||||
|
user: user.id,
|
||||||
|
});
|
||||||
|
expect(post2.toJSON()).toMatchObject({
|
||||||
|
id: 2,
|
||||||
|
name: 'post2',
|
||||||
|
userId: 1,
|
||||||
|
});
|
||||||
|
const post3 = await Post.model.create({ name: 'post3' });
|
||||||
|
await updateAssociations(post3, {
|
||||||
|
user: {
|
||||||
|
name: 'user3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(post3.toJSON()).toMatchObject({
|
||||||
|
id: 3,
|
||||||
|
name: 'post3',
|
||||||
|
userId: 2,
|
||||||
|
user: {
|
||||||
|
id: 2,
|
||||||
|
name: 'user3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const post4 = await Post.model.create({ name: 'post4' });
|
||||||
|
await updateAssociations(post4, {
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
name: 'user4',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(post4.toJSON()).toMatchObject({
|
||||||
|
id: 4,
|
||||||
|
name: 'post4',
|
||||||
|
userId: 1,
|
||||||
|
user: {
|
||||||
|
id: 1,
|
||||||
|
name: 'user1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hasMany', () => {
|
describe('hasMany', () => {
|
||||||
it.only('model', async () => {
|
let db: Database;
|
||||||
const User = db.collection({
|
let User: Collection;
|
||||||
|
let Post: Collection;
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'name' },
|
{ type: 'string', name: 'name' },
|
||||||
{ type: 'hasMany', name: 'posts' },
|
{ type: 'hasMany', name: 'posts' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const Post = db.collection({
|
Post = db.collection({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
schema: [
|
fields: [
|
||||||
{ type: 'string', name: 'title' },
|
{ type: 'string', name: 'name' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await db.sync();
|
await db.sync();
|
||||||
const user = await User.model.create();
|
});
|
||||||
const post1 = await Post.model.create();
|
afterEach(async () => {
|
||||||
const post2 = await Post.model.create<any>();
|
await db.close();
|
||||||
const post3 = await Post.model.create<any>();
|
});
|
||||||
const post4 = await Post.model.create<any>();
|
it('user.posts', async () => {
|
||||||
await updateAssociations(user, {
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
posts: {
|
posts: {
|
||||||
title: 'post0',
|
name: 'post1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await updateAssociations(user, {
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
name: 'post1',
|
||||||
|
userId: user1.id,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('user.posts', async () => {
|
||||||
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
|
posts: [{
|
||||||
|
name: 'post1',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
name: 'post1',
|
||||||
|
userId: user1.id,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('user.posts', async () => {
|
||||||
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
const post1 = await Post.model.create<any>({ name: 'post1' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
|
posts: post1.id,
|
||||||
|
});
|
||||||
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
|
});
|
||||||
|
const post11 = await Post.model.findByPk(post1.id);
|
||||||
|
expect(post11.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('user.posts', async () => {
|
||||||
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
const post1 = await Post.model.create<any>({ name: 'post1' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
posts: post1,
|
posts: post1,
|
||||||
});
|
});
|
||||||
await updateAssociations(user, {
|
console.log(JSON.stringify(user1, null, 2));
|
||||||
posts: post2.id,
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
});
|
});
|
||||||
await updateAssociations(user, {
|
const post11 = await Post.model.findByPk(post1.id);
|
||||||
posts: [post3.id],
|
expect(post11.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
});
|
});
|
||||||
await updateAssociations(user, {
|
});
|
||||||
|
it('user.posts', async () => {
|
||||||
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
const post1 = await Post.model.create<any>({ name: 'post1' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
posts: {
|
posts: {
|
||||||
id: post4.id,
|
id: post1.id,
|
||||||
title: 'post4',
|
name: 'post111',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(JSON.stringify(user1, null, 2));
|
||||||
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
|
});
|
||||||
|
const post11 = await Post.model.findByPk(post1.id);
|
||||||
|
expect(post11.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
|
name: 'post1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('user.posts', async () => {
|
||||||
|
const user1 = await User.model.create<any>({ name: 'user1' });
|
||||||
|
const post1 = await Post.model.create<any>({ name: 'post1' });
|
||||||
|
const post2 = await Post.model.create<any>({ name: 'post2' });
|
||||||
|
const post3 = await Post.model.create<any>({ name: 'post3' });
|
||||||
|
await updateAssociations(user1, {
|
||||||
|
posts: [
|
||||||
|
{
|
||||||
|
id: post1.id,
|
||||||
|
name: 'post111',
|
||||||
|
},
|
||||||
|
post2.id,
|
||||||
|
post3,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
console.log(JSON.stringify(user1, null, 2));
|
||||||
|
expect(user1.toJSON()).toMatchObject({
|
||||||
|
name: 'user1',
|
||||||
|
});
|
||||||
|
const post11 = await Post.model.findByPk(post1.id);
|
||||||
|
expect(post11.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
|
name: 'post1',
|
||||||
|
});
|
||||||
|
const post22 = await Post.model.findByPk(post2.id);
|
||||||
|
expect(post22.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
|
name: 'post2',
|
||||||
|
});
|
||||||
|
const post33 = await Post.model.findByPk(post3.id);
|
||||||
|
expect(post33.toJSON()).toMatchObject({
|
||||||
|
userId: user1.id,
|
||||||
|
name: 'post3',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nested', async () => {
|
describe('nested', () => {
|
||||||
const User = db.collection({
|
let db: Database;
|
||||||
name: 'users',
|
let User: Collection;
|
||||||
schema: [
|
let Post: Collection;
|
||||||
{ type: 'string', name: 'name' },
|
let Comment: Collection;
|
||||||
{ type: 'hasMany', name: 'posts' },
|
|
||||||
],
|
beforeEach(async () => {
|
||||||
|
db = mockDatabase();
|
||||||
|
User = db.collection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'hasMany', name: 'posts' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Post = db.collection({
|
||||||
|
name: 'posts',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'belongsTo', name: 'user' },
|
||||||
|
{ type: 'hasMany', name: 'comments' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
Comment = db.collection({
|
||||||
|
name: 'comments',
|
||||||
|
fields: [
|
||||||
|
{ type: 'string', name: 'name' },
|
||||||
|
{ type: 'belongsTo', name: 'post' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
});
|
});
|
||||||
const Post = db.collection({
|
|
||||||
name: 'posts',
|
afterEach(async () => {
|
||||||
schema: [
|
await db.close();
|
||||||
{ type: 'string', name: 'title' },
|
|
||||||
{ type: 'belongsTo', name: 'user' },
|
|
||||||
{ type: 'hasMany', name: 'comments' },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
const Comment = db.collection({
|
|
||||||
name: 'comments',
|
it('nested', async () => {
|
||||||
schema: [
|
const user = await User.model.create<any>({ name: 'user1' });
|
||||||
{ type: 'string', name: 'content' },
|
await updateAssociations(user, {
|
||||||
{ type: 'belongsTo', name: 'post' },
|
posts: [
|
||||||
],
|
{
|
||||||
});
|
name: 'post1',
|
||||||
await db.sync();
|
comments: [
|
||||||
const user = await User.model.create();
|
{
|
||||||
await updateAssociations(user, {
|
name: 'comment1',
|
||||||
posts: [
|
},
|
||||||
{
|
],
|
||||||
title: 'post1',
|
},
|
||||||
// user: {
|
],
|
||||||
// name: 'user1',
|
});
|
||||||
// },
|
const post1 = await Post.model.findOne({
|
||||||
comments: [
|
where: { name: 'post1' }
|
||||||
{
|
});
|
||||||
content: 'content1',
|
const comment1 = await Comment.model.findOne({
|
||||||
},
|
where: { name: 'comment1' }
|
||||||
],
|
});
|
||||||
},
|
expect(post1).toMatchObject({
|
||||||
],
|
userId: user.get('id'),
|
||||||
|
});
|
||||||
|
expect(comment1).toMatchObject({
|
||||||
|
postId: post1.get('id'),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { ModelCtor, Model } from 'sequelize';
|
import { Sequelize, ModelCtor, Model, DataTypes, Utils } from 'sequelize';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { Schema } from './schema';
|
import { Field } from './fields';
|
||||||
import { RelationField } from './schema-fields';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Repository } from './repository';
|
import { Repository } from './repository';
|
||||||
|
|
||||||
export interface CollectionOptions {
|
export interface CollectionOptions {
|
||||||
schema?: any;
|
name: string;
|
||||||
|
tableName?: string;
|
||||||
|
fields?: any;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,52 +16,103 @@ export interface CollectionContext {
|
|||||||
database: Database;
|
database: Database;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Collection {
|
export class Collection extends EventEmitter {
|
||||||
schema: Schema;
|
|
||||||
model: ModelCtor<Model>;
|
|
||||||
repository: Repository;
|
|
||||||
options: CollectionOptions;
|
options: CollectionOptions;
|
||||||
context: CollectionContext;
|
context: CollectionContext;
|
||||||
|
fields: Map<string, any>;
|
||||||
|
model: ModelCtor<Model>;
|
||||||
|
repository: Repository;
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this.options.name;
|
return this.options.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: CollectionOptions, context: CollectionContext) {
|
constructor(options: CollectionOptions, context?: CollectionContext) {
|
||||||
|
super();
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
const { name, tableName } = options;
|
this.fields = new Map<string, any>();
|
||||||
this.model = class extends Model<any, any> {};
|
this.model = class extends Model<any, any> {};
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
|
const { name, tableName } = options;
|
||||||
// TODO: 不能重复 model.init,如果有涉及 InitOptions 参数修改,需要另外处理。
|
// TODO: 不能重复 model.init,如果有涉及 InitOptions 参数修改,需要另外处理。
|
||||||
this.model.init(attributes, {
|
this.model.init(attributes, {
|
||||||
..._.omit(options, ['name', 'schema']),
|
..._.omit(options, ['name', 'fields']),
|
||||||
sequelize: context.database.sequelize,
|
sequelize: context.database.sequelize,
|
||||||
modelName: name,
|
modelName: name,
|
||||||
tableName: tableName || name,
|
tableName: tableName || name,
|
||||||
});
|
});
|
||||||
// schema 只针对字段,对应 Sequelize 的 Attributes
|
this.on('field.afterAdd', (field) => field.bind());
|
||||||
// 其他 InitOptions 参数放在 Collection 里,通过其他方法同步给 model
|
this.on('field.afterRemove', (field) => field.unbind());
|
||||||
this.schema = new Schema(options.schema, {
|
this.setFields(options.fields);
|
||||||
...context,
|
|
||||||
collection: this,
|
|
||||||
});
|
|
||||||
this.schema2model();
|
|
||||||
this.context.database.emit('collection.init', this);
|
|
||||||
this.repository = new Repository(this);
|
this.repository = new Repository(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
schema2model() {
|
forEachField(callback: (field: Field) => void) {
|
||||||
this.schema.forEach((field) => {
|
return [...this.fields.values()].forEach(callback);
|
||||||
field.bind();
|
}
|
||||||
});
|
|
||||||
this.schema.on('setted', (field) => {
|
findField(callback: (field: Field) => boolean) {
|
||||||
// console.log('setted', field);
|
return [...this.fields.values()].find(callback);
|
||||||
field.bind();
|
}
|
||||||
});
|
|
||||||
this.schema.on('deleted', (field) => field.unbind());
|
hasField(name: string) {
|
||||||
this.schema.on('merged', (field) => {
|
return this.fields.has(name);
|
||||||
//
|
}
|
||||||
|
|
||||||
|
getField(name: string) {
|
||||||
|
return this.fields.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
addField(options) {
|
||||||
|
const { name, ...others } = options;
|
||||||
|
if (!name) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const { database } = this.context;
|
||||||
|
const field = database.buildField({ name, ...others }, {
|
||||||
|
...this.context,
|
||||||
|
collection: this,
|
||||||
|
model: this.model,
|
||||||
});
|
});
|
||||||
|
this.fields.set(name, field);
|
||||||
|
this.emit('field.afterAdd', field);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFields(fields: any, reset = true) {
|
||||||
|
if (!fields) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (reset) {
|
||||||
|
this.fields.clear();
|
||||||
|
}
|
||||||
|
if (Array.isArray(fields)) {
|
||||||
|
for (const field of fields) {
|
||||||
|
this.addField(field);
|
||||||
|
}
|
||||||
|
} else if (typeof fields === 'object') {
|
||||||
|
for (const [name, options] of Object.entries<any>(fields)) {
|
||||||
|
this.addField({...options, name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeField(name) {
|
||||||
|
const field = this.fields.get(name);
|
||||||
|
const bool = this.fields.delete(name);
|
||||||
|
if (bool) {
|
||||||
|
this.emit('field.afterRemove', field);
|
||||||
|
}
|
||||||
|
return bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
extend(options) {
|
||||||
|
const { fields } = options;
|
||||||
|
this.setFields(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
sync() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { Sequelize, ModelCtor, Model, Options, SyncOptions, Op, Utils } from 'sequelize';
|
import {
|
||||||
|
Sequelize,
|
||||||
|
ModelCtor,
|
||||||
|
Model,
|
||||||
|
Options,
|
||||||
|
SyncOptions,
|
||||||
|
Op,
|
||||||
|
Utils,
|
||||||
|
} from 'sequelize';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Collection, CollectionOptions } from './collection';
|
import { Collection, CollectionOptions } from './collection';
|
||||||
import {
|
import * as FieldTypes from './fields';
|
||||||
RelationField,
|
import { RelationField } from './fields';
|
||||||
StringField,
|
|
||||||
HasOneField,
|
|
||||||
HasManyField,
|
|
||||||
BelongsToField,
|
|
||||||
BelongsToManyField,
|
|
||||||
JsonField,
|
|
||||||
JsonbField,
|
|
||||||
} from './schema-fields';
|
|
||||||
|
|
||||||
export interface PendingOptions {
|
export interface PendingOptions {
|
||||||
field: RelationField;
|
field: RelationField;
|
||||||
@ -21,7 +21,7 @@ export type DatabaseOptions = Options | Sequelize;
|
|||||||
|
|
||||||
export class Database extends EventEmitter {
|
export class Database extends EventEmitter {
|
||||||
sequelize: Sequelize;
|
sequelize: Sequelize;
|
||||||
schemaTypes = new Map();
|
fieldTypes = new Map();
|
||||||
models = new Map();
|
models = new Map();
|
||||||
repositories = new Map();
|
repositories = new Map();
|
||||||
operators = new Map();
|
operators = new Map();
|
||||||
@ -30,27 +30,32 @@ export class Database extends EventEmitter {
|
|||||||
|
|
||||||
constructor(options: DatabaseOptions) {
|
constructor(options: DatabaseOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (options instanceof Sequelize) {
|
if (options instanceof Sequelize) {
|
||||||
this.sequelize = options;
|
this.sequelize = options;
|
||||||
} else {
|
} else {
|
||||||
this.sequelize = new Sequelize(options);
|
this.sequelize = new Sequelize(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.collections = new Map();
|
this.collections = new Map();
|
||||||
this.on('collection.init', (collection) => {
|
|
||||||
|
this.on('collection.afterDefine', (collection) => {
|
||||||
const items = this.pendingFields.get(collection.name);
|
const items = this.pendingFields.get(collection.name);
|
||||||
for (const field of items || []) {
|
for (const field of items || []) {
|
||||||
field.bind();
|
field.bind();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.registerSchemaTypes({
|
|
||||||
string: StringField,
|
for (const [name, field] of Object.entries(FieldTypes)) {
|
||||||
json: JsonField,
|
if (['Field', 'RelationField'].includes(name)) {
|
||||||
jsonb: JsonbField,
|
continue;
|
||||||
hasOne: HasOneField,
|
}
|
||||||
hasMany: HasManyField,
|
let key = name.replace(/Field$/g, '');
|
||||||
belongsTo: BelongsToField,
|
key = key.substring(0, 1).toLowerCase() + key.substring(1);
|
||||||
belongsToMany: BelongsToManyField,
|
this.registerFieldTypes({
|
||||||
});
|
[key]: field,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const operators = new Map();
|
const operators = new Map();
|
||||||
|
|
||||||
@ -68,11 +73,12 @@ export class Database extends EventEmitter {
|
|||||||
collection(options: CollectionOptions) {
|
collection(options: CollectionOptions) {
|
||||||
let collection = this.collections.get(options.name);
|
let collection = this.collections.get(options.name);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
collection.schema.set(options.schema);
|
collection.extend(options);
|
||||||
} else {
|
} else {
|
||||||
collection = new Collection(options, { database: this });
|
collection = new Collection(options, { database: this });
|
||||||
}
|
}
|
||||||
this.collections.set(collection.name, collection);
|
this.collections.set(collection.name, collection);
|
||||||
|
this.emit('collection.afterDefine', collection);
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +102,9 @@ export class Database extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSchemaTypes(schemaTypes: any) {
|
registerFieldTypes(fieldTypes: any) {
|
||||||
for (const [type, schemaType] of Object.entries(schemaTypes)) {
|
for (const [type, fieldType] of Object.entries(fieldTypes)) {
|
||||||
this.schemaTypes.set(type, schemaType);
|
this.fieldTypes.set(type, fieldType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +126,9 @@ export class Database extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSchemaField(options, context) {
|
buildField(options, context) {
|
||||||
const { type } = options;
|
const { type } = options;
|
||||||
const Field = this.schemaTypes.get(type);
|
const Field = this.fieldTypes.get(type);
|
||||||
return new Field(options, context);
|
return new Field(options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@ import { Sequelize, ModelCtor, Model, DataTypes, Utils } from 'sequelize';
|
|||||||
import { RelationField } from './relation-field';
|
import { RelationField } from './relation-field';
|
||||||
|
|
||||||
export class BelongsToField extends RelationField {
|
export class BelongsToField extends RelationField {
|
||||||
|
|
||||||
|
static type = 'belongsTo';
|
||||||
|
|
||||||
get target() {
|
get target() {
|
||||||
const { target, name } = this.options;
|
const { target, name } = this.options;
|
||||||
return target || Utils.pluralize(name);
|
return target || Utils.pluralize(name);
|
||||||
@ -38,8 +41,8 @@ export class BelongsToField extends RelationField {
|
|||||||
// 如果外键没有显式的创建,关系表也无反向关联字段,删除关系时,外键也删除掉
|
// 如果外键没有显式的创建,关系表也无反向关联字段,删除关系时,外键也删除掉
|
||||||
const tcoll = database.collections.get(this.target);
|
const tcoll = database.collections.get(this.target);
|
||||||
const foreignKey = this.options.foreignKey;
|
const foreignKey = this.options.foreignKey;
|
||||||
const field1 = collection.schema.get(foreignKey);
|
const field1 = collection.getField(foreignKey);
|
||||||
const field2 = tcoll.schema.find((field) => {
|
const field2 = tcoll.findField((field) => {
|
||||||
return field.type === 'hasMany' && field.foreignKey === foreignKey;
|
return field.type === 'hasMany' && field.foreignKey === foreignKey;
|
||||||
});
|
});
|
||||||
if (!field1 && !field2) {
|
if (!field1 && !field2) {
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class DateField extends SchemaField {
|
export class DateField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.DATE;
|
return DataTypes.DATE;
|
||||||
}
|
}
|
@ -2,19 +2,18 @@ import { Sequelize, ModelCtor, Model, DataTypes, Utils } from 'sequelize';
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Collection } from '../collection';
|
import { Collection } from '../collection';
|
||||||
import { Database } from '../database';
|
import { Database } from '../database';
|
||||||
import { Schema } from '../schema';
|
|
||||||
import { RelationField } from './relation-field';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export interface SchemaFieldContext {
|
export interface FieldContext {
|
||||||
database: Database;
|
database: Database;
|
||||||
collection: Collection;
|
collection: Collection;
|
||||||
schema: Schema;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SchemaField {
|
export abstract class Field {
|
||||||
options: any;
|
options: any;
|
||||||
context: SchemaFieldContext;
|
context: FieldContext;
|
||||||
|
database: Database;
|
||||||
|
collection: Collection;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
@ -29,8 +28,10 @@ export abstract class SchemaField {
|
|||||||
return this.options.dataType;
|
return this.options.dataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options?: any, context?: SchemaFieldContext) {
|
constructor(options?: any, context?: FieldContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.database = context.database;
|
||||||
|
this.collection = context.collection;
|
||||||
this.options = options || {};
|
this.options = options || {};
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class FloatField extends SchemaField {
|
export class FloatField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.FLOAT;
|
return DataTypes.FLOAT;
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ import {
|
|||||||
AssociationScope,
|
AssociationScope,
|
||||||
ForeignKeyOptions,
|
ForeignKeyOptions,
|
||||||
HasManyOptions,
|
HasManyOptions,
|
||||||
|
Utils,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { RelationField } from './relation-field';
|
import { RelationField } from './relation-field';
|
||||||
|
|
||||||
@ -73,6 +74,19 @@ export interface HasManyFieldOptions extends HasManyOptions {
|
|||||||
|
|
||||||
export class HasManyField extends RelationField {
|
export class HasManyField extends RelationField {
|
||||||
|
|
||||||
|
get foreignKey() {
|
||||||
|
if (this.options.foreignKey) {
|
||||||
|
return this.options.foreignKey;
|
||||||
|
}
|
||||||
|
const { model } = this.context.collection;
|
||||||
|
return Utils.camelize(
|
||||||
|
[
|
||||||
|
model.options.name.singular,
|
||||||
|
this.sourceKey || model.primaryKeyAttribute
|
||||||
|
].join('_')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bind() {
|
bind() {
|
||||||
const { database, collection } = this.context;
|
const { database, collection } = this.context;
|
||||||
const Target = this.TargetModel;
|
const Target = this.TargetModel;
|
||||||
@ -82,6 +96,7 @@ export class HasManyField extends RelationField {
|
|||||||
}
|
}
|
||||||
const association = collection.model.hasMany(Target, {
|
const association = collection.model.hasMany(Target, {
|
||||||
as: this.name,
|
as: this.name,
|
||||||
|
foreignKey: this.foreignKey,
|
||||||
...omit(this.options, ['name', 'type', 'target']),
|
...omit(this.options, ['name', 'type', 'target']),
|
||||||
});
|
});
|
||||||
// 建立关系之后从 pending 列表中删除
|
// 建立关系之后从 pending 列表中删除
|
||||||
@ -103,7 +118,7 @@ export class HasManyField extends RelationField {
|
|||||||
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
||||||
const tcoll = database.collections.get(this.target);
|
const tcoll = database.collections.get(this.target);
|
||||||
const foreignKey = this.options.foreignKey;
|
const foreignKey = this.options.foreignKey;
|
||||||
const field = tcoll.schema.find((field) => {
|
const field = tcoll.findField((field) => {
|
||||||
if (field.name === foreignKey) {
|
if (field.name === foreignKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -123,7 +123,7 @@ export class HasOneField extends RelationField {
|
|||||||
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
||||||
const tcoll = database.collections.get(this.target);
|
const tcoll = database.collections.get(this.target);
|
||||||
const foreignKey = this.options.foreignKey;
|
const foreignKey = this.options.foreignKey;
|
||||||
const field = tcoll.schema.find((field) => {
|
const field = tcoll.findField((field) => {
|
||||||
if (field.name === foreignKey) {
|
if (field.name === foreignKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export * from './schema-field';
|
export * from './field';
|
||||||
export * from './string-field';
|
export * from './string-field';
|
||||||
export * from './relation-field'
|
export * from './relation-field'
|
||||||
export * from './belongs-to-field'
|
export * from './belongs-to-field'
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class IntegerField extends SchemaField {
|
export class IntegerField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.INTEGER;
|
return DataTypes.INTEGER;
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class JsonField extends SchemaField {
|
export class JsonField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.JSON;
|
return DataTypes.JSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JsonbField extends SchemaField {
|
export class JsonbField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
const dialect = this.context.database.sequelize.getDialect();
|
const dialect = this.context.database.sequelize.getDialect();
|
||||||
if (dialect === 'postgres') {
|
if (dialect === 'postgres') {
|
@ -1,31 +1,31 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class IntegerField extends SchemaField {
|
export class IntegerField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.INTEGER;
|
return DataTypes.INTEGER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FloatField extends SchemaField {
|
export class FloatField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.FLOAT;
|
return DataTypes.FLOAT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DoubleField extends SchemaField {
|
export class DoubleField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.DOUBLE;
|
return DataTypes.DOUBLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RealField extends SchemaField {
|
export class RealField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.REAL;
|
return DataTypes.REAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DecimalField extends SchemaField {
|
export class DecimalField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.DECIMAL;
|
return DataTypes.DECIMAL;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export abstract class RelationField extends SchemaField {
|
export abstract class RelationField extends Field {
|
||||||
get target() {
|
get target() {
|
||||||
const { target, name } = this.options;
|
const { target, name } = this.options;
|
||||||
return target || name;
|
return target || name;
|
@ -1,8 +1,8 @@
|
|||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class SortField extends SchemaField {
|
export class SortField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.INTEGER;
|
return DataTypes.INTEGER;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class StringField extends SchemaField {
|
export class StringField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.STRING;
|
return DataTypes.STRING;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class TextField extends SchemaField {
|
export class TextField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.TEXT;
|
return DataTypes.TEXT;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class TimeField extends SchemaField {
|
export class TimeField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.TIME;
|
return DataTypes.TIME;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { DataTypes } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { SchemaField } from './schema-field';
|
import { Field } from './field';
|
||||||
|
|
||||||
export class VirtualField extends SchemaField {
|
export class VirtualField extends Field {
|
||||||
get dataType() {
|
get dataType() {
|
||||||
return DataTypes.VIRTUAL;
|
return DataTypes.VIRTUAL;
|
||||||
}
|
}
|
@ -1,21 +1,30 @@
|
|||||||
import {
|
import {
|
||||||
ModelCtor,
|
|
||||||
Model,
|
|
||||||
BulkCreateOptions,
|
|
||||||
FindOptions,
|
|
||||||
Op,
|
Op,
|
||||||
|
Model,
|
||||||
|
ModelCtor,
|
||||||
|
Association,
|
||||||
|
FindOptions,
|
||||||
|
BulkCreateOptions,
|
||||||
|
DestroyOptions as SequelizeDestroyOptions,
|
||||||
|
CreateOptions as SequelizeCreateOptions,
|
||||||
|
UpdateOptions as SequelizeUpdateOptions,
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { flatten } from 'flat';
|
import { flatten } from 'flat';
|
||||||
import { Collection } from './collection';
|
import { Collection } from './collection';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { Database } from './database';
|
import { Database } from './database';
|
||||||
import { updateAssociations } from './update-associations';
|
import { updateAssociations } from './update-associations';
|
||||||
|
import { RelationField } from './fields';
|
||||||
|
|
||||||
export interface IRepository {}
|
export interface IRepository {}
|
||||||
|
|
||||||
interface FindAllOptions extends FindOptions {
|
interface CreateManyOptions extends BulkCreateOptions {}
|
||||||
|
|
||||||
|
interface FindManyOptions extends FindOptions {
|
||||||
filter?: any;
|
filter?: any;
|
||||||
fields?: any;
|
fields?: any;
|
||||||
|
appends?: any;
|
||||||
|
expect?: any;
|
||||||
page?: any;
|
page?: any;
|
||||||
pageSize?: any;
|
pageSize?: any;
|
||||||
sort?: any;
|
sort?: any;
|
||||||
@ -24,23 +33,152 @@ interface FindAllOptions extends FindOptions {
|
|||||||
interface FindOneOptions extends FindOptions {
|
interface FindOneOptions extends FindOptions {
|
||||||
filter?: any;
|
filter?: any;
|
||||||
fields?: any;
|
fields?: any;
|
||||||
|
appends?: any;
|
||||||
|
expect?: any;
|
||||||
sort?: any;
|
sort?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Repository implements IRepository {
|
interface CreateOptions extends SequelizeCreateOptions {
|
||||||
collection: Collection;
|
values?: any;
|
||||||
|
whitelist?: any;
|
||||||
|
blacklist?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateOptions extends SequelizeUpdateOptions {
|
||||||
|
values?: any;
|
||||||
|
whitelist?: any;
|
||||||
|
blacklist?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DestroyOptions extends SequelizeDestroyOptions {
|
||||||
|
filter?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelatedQueryOptions {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
field: RelationField;
|
||||||
|
source: {
|
||||||
|
idOrInstance: any;
|
||||||
|
collection: Collection;
|
||||||
|
};
|
||||||
|
target: {
|
||||||
|
association: Association & {
|
||||||
|
accessors: any;
|
||||||
|
};
|
||||||
|
collection: Collection;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Identity = string | number;
|
||||||
|
|
||||||
|
class RelatedQuery {
|
||||||
|
options: RelatedQueryOptions;
|
||||||
|
sourceInstance: Model;
|
||||||
|
|
||||||
|
constructor(options: RelatedQueryOptions) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSourceInstance() {
|
||||||
|
if (this.sourceInstance) {
|
||||||
|
return this.sourceInstance;
|
||||||
|
}
|
||||||
|
const { idOrInstance, collection } = this.options.source;
|
||||||
|
if (idOrInstance instanceof Model) {
|
||||||
|
return (this.sourceInstance = idOrInstance);
|
||||||
|
}
|
||||||
|
this.sourceInstance = await collection.model.findByPk(idOrInstance);
|
||||||
|
return this.sourceInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findMany(options?: any) {
|
||||||
|
const { collection } = this.options.target;
|
||||||
|
return await collection.repository.findMany(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(options?: any) {
|
||||||
|
const { collection } = this.options.target;
|
||||||
|
return await collection.repository.findOne(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(values?: any, options?: any) {
|
||||||
|
const { association } = this.options.target;
|
||||||
|
const createAccessor = association.accessors.create;
|
||||||
|
const source = await this.getSourceInstance();
|
||||||
|
const instance = await source[createAccessor](values, options);
|
||||||
|
if (!instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateAssociations(instance, values);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(values: any, options?: Identity | Model | UpdateOptions) {
|
||||||
|
const { association, collection } = this.options.target;
|
||||||
|
if (options instanceof Model) {
|
||||||
|
return await collection.repository.update(values, options);
|
||||||
|
}
|
||||||
|
const { field } = this.options;
|
||||||
|
if (field.type === 'hasOne' || field.type === 'belongsTo') {
|
||||||
|
const getAccessor = association.accessors.get;
|
||||||
|
const source = await this.getSourceInstance();
|
||||||
|
const instance = await source[getAccessor]();
|
||||||
|
return await collection.repository.update(values, instance);
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
return await collection.repository.update(values, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy(options?: any) {
|
||||||
|
const { association, collection } = this.options.target;
|
||||||
|
const { field } = this.options;
|
||||||
|
if (field.type === 'hasOne' || field.type === 'belongsTo') {
|
||||||
|
const getAccessor = association.accessors.get;
|
||||||
|
const source = await this.getSourceInstance();
|
||||||
|
const instance = await source[getAccessor]();
|
||||||
|
if (!instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await collection.repository.destroy(instance.id);
|
||||||
|
}
|
||||||
|
return await collection.repository.destroy(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(options?: any) {}
|
||||||
|
|
||||||
|
async add(options?: any) {}
|
||||||
|
|
||||||
|
async remove(options?: any) {}
|
||||||
|
|
||||||
|
async toggle(options?: any) {}
|
||||||
|
|
||||||
|
async sync(options?: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HasOneQuery extends RelatedQuery {}
|
||||||
|
|
||||||
|
class HasManyQuery extends RelatedQuery {}
|
||||||
|
|
||||||
|
class BelongsToQuery extends RelatedQuery {}
|
||||||
|
|
||||||
|
class BelongsToManyQuery extends RelatedQuery {}
|
||||||
|
|
||||||
|
export class Repository implements IRepository {
|
||||||
|
database: Database;
|
||||||
|
collection: Collection;
|
||||||
|
model: ModelCtor<Model>;
|
||||||
|
|
||||||
constructor(collection: Collection) {
|
constructor(collection: Collection) {
|
||||||
this.database = collection.context.database;
|
this.database = collection.context.database;
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
|
this.model = collection.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(options?: FindAllOptions) {
|
async findMany(options?: FindManyOptions) {
|
||||||
const model = this.collection.model;
|
const model = this.collection.model;
|
||||||
const opts = {
|
const opts = {
|
||||||
subQuery: false,
|
subQuery: false,
|
||||||
...this.parseApiJson(options),
|
...this.buildQueryOptions(options),
|
||||||
};
|
};
|
||||||
let rows = [];
|
let rows = [];
|
||||||
if (opts.include) {
|
if (opts.include) {
|
||||||
@ -52,14 +190,16 @@ export class Repository implements IRepository {
|
|||||||
group: `${model.name}.${model.primaryKeyAttribute}`,
|
group: `${model.name}.${model.primaryKeyAttribute}`,
|
||||||
})
|
})
|
||||||
).map((item) => item[model.primaryKeyAttribute]);
|
).map((item) => item[model.primaryKeyAttribute]);
|
||||||
rows = await model.findAll({
|
if (ids.length > 0) {
|
||||||
...opts,
|
rows = await model.findAll({
|
||||||
where: {
|
...opts,
|
||||||
[model.primaryKeyAttribute]: {
|
where: {
|
||||||
[Op.in]: ids,
|
[model.primaryKeyAttribute]: {
|
||||||
|
[Op.in]: ids,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
rows = await model.findAll({
|
rows = await model.findAll({
|
||||||
...opts,
|
...opts,
|
||||||
@ -73,19 +213,44 @@ export class Repository implements IRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findOne(options?: FindOneOptions) {
|
async findOne(options?: FindOneOptions) {
|
||||||
const opts = this.parseApiJson(options);
|
const model = this.collection.model;
|
||||||
console.log({ opts });
|
const opts = {
|
||||||
const data = await this.collection.model.findOne(opts);
|
subQuery: false,
|
||||||
|
...this.buildQueryOptions(options),
|
||||||
|
};
|
||||||
|
let data: Model;
|
||||||
|
if (opts.include) {
|
||||||
|
const item = await model.findOne({
|
||||||
|
...opts,
|
||||||
|
includeIgnoreAttributes: false,
|
||||||
|
attributes: [model.primaryKeyAttribute],
|
||||||
|
group: `${model.name}.${model.primaryKeyAttribute}`,
|
||||||
|
});
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = await model.findOne({
|
||||||
|
...opts,
|
||||||
|
where: item.toJSON(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data = await model.findOne({
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {}
|
async create(values?: any, options?: CreateOptions) {
|
||||||
|
const instance = await this.model.create<any>(values, options);
|
||||||
|
if (!instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateAssociations(instance, values, options);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
update() {}
|
async createMany(records: any[], options?: CreateManyOptions) {
|
||||||
|
|
||||||
destroy() {}
|
|
||||||
|
|
||||||
async bulkCreate(records: any[], options?: BulkCreateOptions) {
|
|
||||||
const instances = await this.collection.model.bulkCreate(records, options);
|
const instances = await this.collection.model.bulkCreate(records, options);
|
||||||
const promises = instances.map((instance, index) => {
|
const promises = instances.map((instance, index) => {
|
||||||
return updateAssociations(instance, records[index]);
|
return updateAssociations(instance, records[index]);
|
||||||
@ -93,9 +258,97 @@ export class Repository implements IRepository {
|
|||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseApiJson(options: any) {
|
async update(values: any, options: Identity | Model | UpdateOptions) {
|
||||||
const filter = options.filter || {};
|
if (options instanceof Model) {
|
||||||
|
await options.update(values);
|
||||||
|
await updateAssociations(options, values);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
let instance: Model;
|
||||||
|
if (typeof options === 'string' || typeof options === 'number') {
|
||||||
|
instance = await this.model.findByPk(options);
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
instance = await this.findOne(options);
|
||||||
|
}
|
||||||
|
await instance.update(values);
|
||||||
|
await updateAssociations(instance, values);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy(options: Identity | Identity[] | DestroyOptions) {
|
||||||
|
if (typeof options === 'number' || typeof options === 'string') {
|
||||||
|
return await this.model.destroy({
|
||||||
|
where: {
|
||||||
|
[this.model.primaryKeyAttribute]: options,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
return await this.model.destroy({
|
||||||
|
where: {
|
||||||
|
[this.model.primaryKeyAttribute]: {
|
||||||
|
[Op.in]: options,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const opts = this.buildQueryOptions(options);
|
||||||
|
return await this.model.destroy(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
async sort() {}
|
||||||
|
|
||||||
|
relatedQuery(name: string) {
|
||||||
|
return {
|
||||||
|
for: (sourceIdOrInstance: any) => {
|
||||||
|
const field = this.collection.getField(name) as RelationField;
|
||||||
|
const database = this.collection.context.database;
|
||||||
|
const collection = database.getCollection(field.target);
|
||||||
|
const options: RelatedQueryOptions = {
|
||||||
|
field,
|
||||||
|
database: database,
|
||||||
|
source: {
|
||||||
|
collection: this.collection,
|
||||||
|
idOrInstance: sourceIdOrInstance,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
collection,
|
||||||
|
association: this.collection.model.associations[name] as any,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
switch (field.type) {
|
||||||
|
case 'hasOne':
|
||||||
|
return new HasOneQuery(options);
|
||||||
|
case 'hasMany':
|
||||||
|
return new HasManyQuery(options);
|
||||||
|
case 'belongsTo':
|
||||||
|
return new BelongsToQuery(options);
|
||||||
|
case 'belongsToMany':
|
||||||
|
return new BelongsToManyQuery(options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buildQueryOptions(options: any) {
|
||||||
|
const opts = this.parseFilter(options.filter);
|
||||||
|
return { ...options, ...opts };
|
||||||
|
}
|
||||||
|
|
||||||
|
parseFilter(filter?: any) {
|
||||||
|
if (!filter) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
const model = this.collection.model;
|
const model = this.collection.model;
|
||||||
|
if (typeof filter === 'number' || typeof filter === 'string') {
|
||||||
|
return {
|
||||||
|
where: {
|
||||||
|
[model.primaryKeyAttribute]: filter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
const operators = this.database.operators;
|
const operators = this.database.operators;
|
||||||
const obj = flatten(filter || {});
|
const obj = flatten(filter || {});
|
||||||
const include = {};
|
const include = {};
|
||||||
@ -145,6 +398,7 @@ export class Repository implements IRepository {
|
|||||||
associationKeys.push(k);
|
associationKeys.push(k);
|
||||||
_.set(include, k, {
|
_.set(include, k, {
|
||||||
association: k,
|
association: k,
|
||||||
|
attributes: [],
|
||||||
});
|
});
|
||||||
let target = associations[k].target;
|
let target = associations[k].target;
|
||||||
while (target) {
|
while (target) {
|
||||||
@ -163,6 +417,7 @@ export class Repository implements IRepository {
|
|||||||
});
|
});
|
||||||
_.set(include, assoc, {
|
_.set(include, assoc, {
|
||||||
association: attr,
|
association: attr,
|
||||||
|
attributes: [],
|
||||||
});
|
});
|
||||||
target = target.associations[attr].target;
|
target = target.associations[attr].target;
|
||||||
}
|
}
|
||||||
@ -193,7 +448,6 @@ export class Repository implements IRepository {
|
|||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
console.log(JSON.stringify({ include: toInclude(include) }, null, 2));
|
return { where, include: toInclude(include) };
|
||||||
return { ...options, where, include: toInclude(include) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
import { Sequelize, ModelCtor, Model, DataTypes, Utils } from 'sequelize';
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
import { Database } from './database';
|
|
||||||
import { Collection } from './collection';
|
|
||||||
import { SchemaField } from './schema-fields';
|
|
||||||
|
|
||||||
export interface SchemaContext {
|
|
||||||
database: Database;
|
|
||||||
collection: Collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Schema extends EventEmitter {
|
|
||||||
fields: Map<string, any>;
|
|
||||||
context: SchemaContext;
|
|
||||||
options: any;
|
|
||||||
|
|
||||||
constructor(options?: any, context?: SchemaContext) {
|
|
||||||
super();
|
|
||||||
this.options = options;
|
|
||||||
this.context = context;
|
|
||||||
this.fields = new Map<string, any>();
|
|
||||||
this.set(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
has(name: string) {
|
|
||||||
return this.fields.has(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(name: string) {
|
|
||||||
return this.fields.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
set(name: string | object, obj?: any) {
|
|
||||||
if (!name) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (typeof name === 'string') {
|
|
||||||
const { database } = this.context;
|
|
||||||
const field = database.buildSchemaField({ name, ...obj }, {
|
|
||||||
...this.context,
|
|
||||||
schema: this,
|
|
||||||
model: this.context.collection.model,
|
|
||||||
});
|
|
||||||
// console.log('field', field);
|
|
||||||
this.fields.set(name, field);
|
|
||||||
this.emit('setted', field);
|
|
||||||
} else if (Array.isArray(name)) {
|
|
||||||
for (const value of name) {
|
|
||||||
this.set(value.name, value);
|
|
||||||
}
|
|
||||||
} else if (typeof name === 'object') {
|
|
||||||
for (const [key, value] of Object.entries(name)) {
|
|
||||||
console.log({ key, value })
|
|
||||||
this.set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(name: string) {
|
|
||||||
const field = this.fields.get(name);
|
|
||||||
const bool = this.fields.delete(name);
|
|
||||||
if (bool) {
|
|
||||||
this.emit('deleted', field);
|
|
||||||
}
|
|
||||||
return bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
merge(name: string, obj) {
|
|
||||||
const field = this.get(name);
|
|
||||||
field.merge(obj);
|
|
||||||
this.emit('merged', field);
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(callback: (field: SchemaField) => void) {
|
|
||||||
return [...this.fields.values()].forEach(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
find(callback: (field: SchemaField) => boolean) {
|
|
||||||
return [...this.fields.values()].find(callback);
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,18 +16,18 @@ function isStringOrNumber(value: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAssociations(
|
export async function updateAssociations(
|
||||||
model: Model,
|
instance: Model,
|
||||||
values: any,
|
values: any,
|
||||||
options: any = {},
|
options: any = {},
|
||||||
) {
|
) {
|
||||||
const { transaction = await model.sequelize.transaction() } = options;
|
const { transaction = await instance.sequelize.transaction() } = options;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (const key of Object.keys(model.constructor.associations)) {
|
for (const key of Object.keys(instance.constructor.associations)) {
|
||||||
// 如果 key 不存在才跳过
|
// 如果 key 不存在才跳过
|
||||||
if (!Object.keys(values).includes(key)) {
|
if (!Object.keys(values||{}).includes(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await updateAssociation(model, key, values[key], {
|
await updateAssociation(instance, key, values[key], {
|
||||||
...options,
|
...options,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
@ -38,23 +38,23 @@ export async function updateAssociations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAssociation(
|
export async function updateAssociation(
|
||||||
model: Model,
|
instance: Model,
|
||||||
key: string,
|
key: string,
|
||||||
value: any,
|
value: any,
|
||||||
options: any = {},
|
options: any = {},
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const association = model.constructor.associations[key] as Association;
|
const association = instance.constructor.associations[key] as Association;
|
||||||
if (!association) {
|
if (!association) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (association.associationType) {
|
switch (association.associationType) {
|
||||||
case 'HasOne':
|
case 'HasOne':
|
||||||
case 'BelongsTo':
|
case 'BelongsTo':
|
||||||
return updateSingleAssociation(model, key, value, options);
|
return updateSingleAssociation(instance, key, value, options);
|
||||||
case 'HasMany':
|
case 'HasMany':
|
||||||
case 'BelongsToMany':
|
case 'BelongsToMany':
|
||||||
return updateMultipleAssociation(model, key, value, options);
|
return updateMultipleAssociation(instance, key, value, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,39 +77,63 @@ export async function updateSingleAssociation(
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const setAccessor = association.accessors.set;
|
const setAccessor = association.accessors.set;
|
||||||
if (isUndefinedOrNull(value)) {
|
if (isUndefinedOrNull(value)) {
|
||||||
return await model[setAccessor](null, { transaction });
|
await model[setAccessor](null, { transaction });
|
||||||
|
model.setDataValue(key, null);
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (isStringOrNumber(value)) {
|
if (isStringOrNumber(value)) {
|
||||||
return await model[setAccessor](value, { transaction });
|
await model[setAccessor](value, { transaction });
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value instanceof Model) {
|
||||||
|
await model[setAccessor](value);
|
||||||
|
model.setDataValue(key, value);
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const createAccessor = association.accessors.create;
|
const createAccessor = association.accessors.create;
|
||||||
let key: string;
|
let dataKey: string;
|
||||||
let M: ModelCtor<Model>;
|
let M: ModelCtor<Model>;
|
||||||
if (association.associationType === 'BelongsTo') {
|
if (association.associationType === 'BelongsTo') {
|
||||||
// @ts-ignore
|
|
||||||
key = association.targetKey;
|
|
||||||
M = association.target;
|
M = association.target;
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
key = association.sourceKey;
|
dataKey = association.targetKey;
|
||||||
|
} else {
|
||||||
M = association.source;
|
M = association.source;
|
||||||
|
dataKey = M.primaryKeyAttribute;
|
||||||
}
|
}
|
||||||
if (isStringOrNumber(value)) {
|
if (isStringOrNumber(value[dataKey])) {
|
||||||
let instance: any = await M.findOne({
|
let instance: any = await M.findOne({
|
||||||
where: {
|
where: {
|
||||||
[key]: value[key],
|
[dataKey]: value[dataKey],
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
if (!instance) {
|
if (instance) {
|
||||||
instance = await M.create(value, { transaction });
|
await model[setAccessor](instance);
|
||||||
|
await updateAssociations(instance, value, { transaction, ...options });
|
||||||
|
model.setDataValue(key, instance);
|
||||||
|
if (!options.transaction) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
await model[setAccessor](value[key]);
|
}
|
||||||
await updateAssociations(instance, value, { transaction, ...options });
|
const instance = await model[createAccessor](value, { transaction });
|
||||||
} else {
|
await updateAssociations(instance, value, { transaction, ...options });
|
||||||
const instance = await model[createAccessor](value, { transaction });
|
model.setDataValue(key, instance);
|
||||||
await updateAssociations(instance, value, { transaction, ...options });
|
// @ts-ignore
|
||||||
|
if (association.targetKey) {
|
||||||
|
model.setDataValue(association.foreignKey, instance[dataKey]);
|
||||||
}
|
}
|
||||||
if (!options.transaction) {
|
if (!options.transaction) {
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
@ -143,10 +167,13 @@ export async function updateMultipleAssociation(
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const createAccessor = association.accessors.create;
|
const createAccessor = association.accessors.create;
|
||||||
if (isUndefinedOrNull(value)) {
|
if (isUndefinedOrNull(value)) {
|
||||||
return await model[setAccessor](null, { transaction });
|
await model[setAccessor](null, { transaction });
|
||||||
|
model.setDataValue(key, null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (isStringOrNumber(value)) {
|
if (isStringOrNumber(value)) {
|
||||||
return await model[setAccessor](value, { transaction });
|
await model[setAccessor](value, { transaction });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
value = [value];
|
value = [value];
|
||||||
@ -167,21 +194,24 @@ export async function updateMultipleAssociation(
|
|||||||
list2.push(item);
|
list2.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('updateMultipleAssociation', list1, list2);
|
|
||||||
await model[setAccessor](list1, { transaction });
|
await model[setAccessor](list1, { transaction });
|
||||||
|
const list3 = [];
|
||||||
for (const item of list2) {
|
for (const item of list2) {
|
||||||
const pk = association.target.primaryKeyAttribute;
|
const pk = association.target.primaryKeyAttribute;
|
||||||
if (isUndefinedOrNull(item[pk])) {
|
if (isUndefinedOrNull(item[pk])) {
|
||||||
const instance = await model[createAccessor](item, { transaction });
|
const instance = await model[createAccessor](item, { transaction });
|
||||||
await updateAssociations(instance, item, { transaction, ...options });
|
await updateAssociations(instance, item, { transaction, ...options });
|
||||||
|
list3.push(instance);
|
||||||
} else {
|
} else {
|
||||||
const instance = await association.target.findByPk(item[pk], { transaction });
|
const instance = await association.target.findByPk(item[pk], { transaction });
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const addAccessor = association.accessors.add;
|
const addAccessor = association.accessors.add;
|
||||||
await model[addAccessor](item[pk], { transaction });
|
await model[addAccessor](item[pk], { transaction });
|
||||||
await updateAssociations(instance, item, { transaction, ...options });
|
await updateAssociations(instance, item, { transaction, ...options });
|
||||||
|
list3.push(instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
model.setDataValue(key, list1.concat(list3));
|
||||||
if (!options.transaction) {
|
if (!options.transaction) {
|
||||||
await transaction.commit();
|
await transaction.commit();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user