feat: filter by target key (#146)
* feat: filter by target key * fix: repository test * change type name * chore: test * change PrimaryKey type to TargetKey * rename filterTargetKey * rename variables * change option parser constructor * add option parser targetKey * change filter parser constructor * fix: custom model Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
		
							parent
							
								
									79ba391aee
								
							
						
					
					
						commit
						2bf09bf9bb
					
				@ -1,5 +1,6 @@
 | 
				
			|||||||
import { mockDatabase } from './index';
 | 
					import { mockDatabase } from './index';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					import { Model } from '..';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('database', () => {
 | 
					describe('database', () => {
 | 
				
			||||||
  test('import', async () => {
 | 
					  test('import', async () => {
 | 
				
			||||||
@ -165,4 +166,30 @@ describe('database', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(listener).toHaveBeenCalled();
 | 
					    expect(listener).toHaveBeenCalled();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('custom model', async () => {
 | 
				
			||||||
 | 
					    class CustomModel extends Model {
 | 
				
			||||||
 | 
					      customMethod() {
 | 
				
			||||||
 | 
					        this.setDataValue('abc', 'abc');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const db = mockDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.registerModels({
 | 
				
			||||||
 | 
					      CustomModel,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Test = db.collection({
 | 
				
			||||||
 | 
					      name: 'tests',
 | 
				
			||||||
 | 
					      model: 'CustomModel',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await Test.sync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const test = await Test.model.create<any>();
 | 
				
			||||||
 | 
					    test.customMethod();
 | 
				
			||||||
 | 
					    expect(test.get('abc')).toBe('abc');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@ describe('context field', () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
					      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
      const [t2] = await Test.repository.update({
 | 
					      const [t2] = await Test.repository.update({
 | 
				
			||||||
        filterByPk: t1.get('id') as any,
 | 
					        filterByTk: t1.get('id') as any,
 | 
				
			||||||
        values: {},
 | 
					        values: {},
 | 
				
			||||||
        context: {
 | 
					        context: {
 | 
				
			||||||
          request: {
 | 
					          request: {
 | 
				
			||||||
@ -124,7 +124,7 @@ describe('context field', () => {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
					      expect(t1.get('clientIp')).toBe('11.22.33.44');
 | 
				
			||||||
      const [t2] = await Test.repository.update({
 | 
					      const [t2] = await Test.repository.update({
 | 
				
			||||||
        filterByPk: t1.get('id') as any,
 | 
					        filterByTk: t1.get('id') as any,
 | 
				
			||||||
        values: {},
 | 
					        values: {},
 | 
				
			||||||
        context: {
 | 
					        context: {
 | 
				
			||||||
          request: {
 | 
					          request: {
 | 
				
			||||||
 | 
				
			|||||||
@ -12,9 +12,14 @@ test('filter item by string', async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  await database.sync();
 | 
					  await database.sync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const filterParser = new FilterParser(UserCollection.model, UserCollection.context.database, {
 | 
					  const filterParser = new FilterParser(
 | 
				
			||||||
    name: 'hello',
 | 
					    {
 | 
				
			||||||
  });
 | 
					      name: 'hello',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      collection: UserCollection,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const filterParams = filterParser.toSequelizeParams();
 | 
					  const filterParams = filterParser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,11 +79,9 @@ describe('filter by related', () => {
 | 
				
			|||||||
      'posts.title.$iLike': '%hello%',
 | 
					      'posts.title.$iLike': '%hello%',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterParser = new FilterParser(
 | 
					    const filterParser = new FilterParser(filter, {
 | 
				
			||||||
      db.getCollection('users').model,
 | 
					      collection: db.getCollection('users'),
 | 
				
			||||||
      db.getCollection('users').context.database,
 | 
					    });
 | 
				
			||||||
      filter,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterParams = filterParser.toSequelizeParams();
 | 
					    const filterParams = filterParser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,11 +94,9 @@ describe('filter by related', () => {
 | 
				
			|||||||
      'posts.comments.content.$iLike': '%hello%',
 | 
					      'posts.comments.content.$iLike': '%hello%',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterParser = new FilterParser(
 | 
					    const filterParser = new FilterParser(filter, {
 | 
				
			||||||
      db.getCollection('users').model,
 | 
					      collection: db.getCollection('users'),
 | 
				
			||||||
      db.getCollection('users').context.database,
 | 
					    });
 | 
				
			||||||
      filter,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterParams = filterParser.toSequelizeParams();
 | 
					    const filterParams = filterParser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,9 +13,7 @@ describe('array field operator', function () {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(async () => {
 | 
					  beforeEach(async () => {
 | 
				
			||||||
    db = mockDatabase({
 | 
					    db = mockDatabase({});
 | 
				
			||||||
      logging: console.log,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Test = db.collection({
 | 
					    Test = db.collection({
 | 
				
			||||||
      name: 'test',
 | 
					      name: 'test',
 | 
				
			||||||
@ -66,7 +64,7 @@ describe('array field operator', function () {
 | 
				
			|||||||
    expect(result.get('id')).toEqual(p1.get('id'));
 | 
					    expect(result.get('id')).toEqual(p1.get('id'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await Post.repository.update({
 | 
					    await Post.repository.update({
 | 
				
			||||||
      filterByPk: <any>p1.get('id'),
 | 
					      filterByTk: <any>p1.get('id'),
 | 
				
			||||||
      values: {
 | 
					      values: {
 | 
				
			||||||
        tags: ['t3', 't2'],
 | 
					        tags: ['t3', 't2'],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
				
			|||||||
@ -60,7 +60,10 @@ describe('option parser', () => {
 | 
				
			|||||||
      fields: ['id', 'name', 'tags.id', 'tags.name'],
 | 
					      fields: ['id', 'name', 'tags.id', 'tags.name'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const parser = new OptionsParser(Post.model, Post.context.database, options);
 | 
					    const parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: Post,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const params = parser.toSequelizeParams();
 | 
					    const params = parser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(params).toEqual({
 | 
					    expect(params).toEqual({
 | 
				
			||||||
@ -78,7 +81,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      sort: ['id'],
 | 
					      sort: ['id'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    let parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    let params = parser.toSequelizeParams();
 | 
					    let params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['order']).toEqual([['id', 'ASC']]);
 | 
					    expect(params['order']).toEqual([['id', 'ASC']]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,7 +91,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      sort: ['id', '-posts.title', 'posts.comments.createdAt'],
 | 
					      sort: ['id', '-posts.title', 'posts.comments.createdAt'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['order']).toEqual([
 | 
					    expect(params['order']).toEqual([
 | 
				
			||||||
      ['id', 'ASC'],
 | 
					      ['id', 'ASC'],
 | 
				
			||||||
@ -100,7 +107,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      fields: ['id', 'posts'],
 | 
					      fields: ['id', 'posts'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    // 转换为 attributes: ['id'], include: [{association: 'posts'}]
 | 
					    // 转换为 attributes: ['id'], include: [{association: 'posts'}]
 | 
				
			||||||
    let parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    let parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    let params = parser.toSequelizeParams();
 | 
					    let params = parser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(params['attributes']).toContain('id');
 | 
					    expect(params['attributes']).toContain('id');
 | 
				
			||||||
@ -111,7 +120,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      appends: ['posts'],
 | 
					      appends: ['posts'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['attributes']['include']).toEqual([]);
 | 
					    expect(params['attributes']['include']).toEqual([]);
 | 
				
			||||||
    expect(params['include'][0]['association']).toEqual('posts');
 | 
					    expect(params['include'][0]['association']).toEqual('posts');
 | 
				
			||||||
@ -121,7 +132,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      fields: ['id', 'posts.title'],
 | 
					      fields: ['id', 'posts.title'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['attributes']).toContain('id');
 | 
					    expect(params['attributes']).toContain('id');
 | 
				
			||||||
    expect(params['include'][0]['association']).toEqual('posts');
 | 
					    expect(params['include'][0]['association']).toEqual('posts');
 | 
				
			||||||
@ -132,7 +145,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      fields: ['id', 'posts', 'posts.comments.content'],
 | 
					      fields: ['id', 'posts', 'posts.comments.content'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['attributes']).toContain('id');
 | 
					    expect(params['attributes']).toContain('id');
 | 
				
			||||||
    expect(params['include'][0]['association']).toEqual('posts');
 | 
					    expect(params['include'][0]['association']).toEqual('posts');
 | 
				
			||||||
@ -143,7 +158,9 @@ describe('option parser', () => {
 | 
				
			|||||||
    options = {
 | 
					    options = {
 | 
				
			||||||
      except: ['id'],
 | 
					      except: ['id'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
    expect(params['attributes']['exclude']).toContain('id');
 | 
					    expect(params['attributes']['exclude']).toContain('id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -153,7 +170,9 @@ describe('option parser', () => {
 | 
				
			|||||||
      except: ['posts.id'],
 | 
					      except: ['posts.id'],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = new OptionsParser(User.model, User.context.database, options);
 | 
					    parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: User,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    params = parser.toSequelizeParams();
 | 
					    params = parser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(params['include'][0]['attributes']['exclude']).toContain('id');
 | 
					    expect(params['include'][0]['attributes']['exclude']).toContain('id');
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,113 @@
 | 
				
			|||||||
import { mockDatabase } from '../index';
 | 
					import { mockDatabase } from '../index';
 | 
				
			||||||
import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
 | 
					import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
 | 
				
			||||||
import Database from '../../database';
 | 
					import Database from '../../database';
 | 
				
			||||||
 | 
					import { Collection, HasManyRepository } from '@nocobase/database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('belongs to many with target key', function () {
 | 
				
			||||||
 | 
					  let db: Database;
 | 
				
			||||||
 | 
					  let Tag: Collection;
 | 
				
			||||||
 | 
					  let Post: Collection;
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    db = mockDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Post = db.collection({
 | 
				
			||||||
 | 
					      name: 'posts',
 | 
				
			||||||
 | 
					      filterTargetKey: 'title',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'title', primaryKey: true },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          type: 'belongsToMany',
 | 
				
			||||||
 | 
					          name: 'tags',
 | 
				
			||||||
 | 
					          sourceKey: 'title',
 | 
				
			||||||
 | 
					          foreignKey: 'postTitle',
 | 
				
			||||||
 | 
					          targetKey: 'name',
 | 
				
			||||||
 | 
					          otherKey: 'tagName',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Tag = db.collection({
 | 
				
			||||||
 | 
					      name: 'tags',
 | 
				
			||||||
 | 
					      filterTargetKey: 'name',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'name', primaryKey: true },
 | 
				
			||||||
 | 
					        { type: 'string', name: 'status' },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.sync({ force: true });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    await db.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('destroy by target key', async () => {
 | 
				
			||||||
 | 
					    const t1 = await Tag.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 't1',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const t2 = await Tag.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 't2',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p1 = await Post.repository.create({
 | 
				
			||||||
 | 
					      values: { title: 'p1' },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.get('title') as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await PostTagRepository.set([t1.get('name') as string, t2.get('name')]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await PostTagRepository.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [_, count] = await PostTagRepository.findAndCount();
 | 
				
			||||||
 | 
					    expect(count).toEqual(0);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('destroy with target key and filter', async () => {
 | 
				
			||||||
 | 
					    let t1 = await Tag.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 't1',
 | 
				
			||||||
 | 
					        status: 'published',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const t2 = await Tag.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 't2',
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p1 = await Post.repository.create({
 | 
				
			||||||
 | 
					      values: { title: 'p1' },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.get('title') as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await PostTagRepository.set([t1.get('name') as string, t2.get('name') as string]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let [_, count] = await PostTagRepository.findAndCount();
 | 
				
			||||||
 | 
					    expect(count).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await PostTagRepository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: t1.get('name') as string,
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [_, count] = await PostTagRepository.findAndCount();
 | 
				
			||||||
 | 
					    expect(count).toEqual(2);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('belongs to many', () => {
 | 
					describe('belongs to many', () => {
 | 
				
			||||||
  let db: Database;
 | 
					  let db: Database;
 | 
				
			||||||
@ -51,6 +158,7 @@ describe('belongs to many', () => {
 | 
				
			|||||||
      ],
 | 
					      ],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.sequelize.getQueryInterface().dropAllTables();
 | 
				
			||||||
    await db.sync({ force: true });
 | 
					    await db.sync({ force: true });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -384,7 +492,7 @@ describe('belongs to many', () => {
 | 
				
			|||||||
    await PostTagRepository.set([t1.id, t2.id]);
 | 
					    await PostTagRepository.set([t1.id, t2.id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const findByPkResult = await PostTagRepository.findOne({
 | 
					    const findByPkResult = await PostTagRepository.findOne({
 | 
				
			||||||
      filterByPk: t2.id,
 | 
					      filterByTk: t2.id,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(findByPkResult.name).toEqual('t2');
 | 
					    expect(findByPkResult.name).toEqual('t2');
 | 
				
			||||||
@ -480,7 +588,7 @@ describe('belongs to many', () => {
 | 
				
			|||||||
    expect(count).toEqual(2);
 | 
					    expect(count).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await PostTagRepository.destroy({
 | 
					    await PostTagRepository.destroy({
 | 
				
			||||||
      filterByPk: t1.get('id') as number,
 | 
					      filterByTk: t1.get('id') as number,
 | 
				
			||||||
      filter: {
 | 
					      filter: {
 | 
				
			||||||
        status: 'draft',
 | 
					        status: 'draft',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -511,10 +619,11 @@ describe('belongs to many', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await PostTagRepository.set([t1.id, t2.id]);
 | 
					    await PostTagRepository.set([t1.id, t2.id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await PostTagRepository.count()).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await PostTagRepository.destroy(t2.id);
 | 
					    await PostTagRepository.destroy(t2.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const result = await PostTagRepository.findAndCount();
 | 
					    expect(await PostTagRepository.count()).toEqual(1);
 | 
				
			||||||
    expect(result[1]).toEqual(1);
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('transaction', async () => {
 | 
					  test('transaction', async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,116 @@ import { mockDatabase } from '../index';
 | 
				
			|||||||
import { HasManyRepository } from '../../relation-repository/hasmany-repository';
 | 
					import { HasManyRepository } from '../../relation-repository/hasmany-repository';
 | 
				
			||||||
import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
 | 
					import { BelongsToManyRepository } from '../../relation-repository/belongs-to-many-repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('has many with target key', function () {
 | 
				
			||||||
 | 
					  test('target key with filterTargetKey', async () => {
 | 
				
			||||||
 | 
					    const db = mockDatabase();
 | 
				
			||||||
 | 
					    const User = db.collection<{ id: number; name: string }, { name: string }>({
 | 
				
			||||||
 | 
					      name: 'users',
 | 
				
			||||||
 | 
					      filterTargetKey: 'name',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'name', unique: true },
 | 
				
			||||||
 | 
					        { type: 'integer', name: 'age' },
 | 
				
			||||||
 | 
					        { type: 'hasMany', name: 'posts', sourceKey: 'name', foreignKey: 'userName', targetKey: 'title' },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Post = db.collection({
 | 
				
			||||||
 | 
					      name: 'posts',
 | 
				
			||||||
 | 
					      filterTargetKey: 'title',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'title', unique: true },
 | 
				
			||||||
 | 
					        { type: 'string', name: 'status' },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          name: 'user',
 | 
				
			||||||
 | 
					          targetKey: 'name',
 | 
				
			||||||
 | 
					          foreignKey: 'userName',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('destroy by target key and filter', async () => {
 | 
				
			||||||
 | 
					    const db = mockDatabase();
 | 
				
			||||||
 | 
					    const User = db.collection<{ id: number; name: string }, { name: string }>({
 | 
				
			||||||
 | 
					      name: 'users',
 | 
				
			||||||
 | 
					      filterTargetKey: 'name',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'name', unique: true },
 | 
				
			||||||
 | 
					        { type: 'integer', name: 'age' },
 | 
				
			||||||
 | 
					        { type: 'hasMany', name: 'posts', sourceKey: 'name', foreignKey: 'userName', targetKey: 'title' },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const Post = db.collection({
 | 
				
			||||||
 | 
					      name: 'posts',
 | 
				
			||||||
 | 
					      filterTargetKey: 'title',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'title', unique: true },
 | 
				
			||||||
 | 
					        { type: 'string', name: 'status' },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          type: 'belongsTo',
 | 
				
			||||||
 | 
					          name: 'user',
 | 
				
			||||||
 | 
					          targetKey: 'name',
 | 
				
			||||||
 | 
					          foreignKey: 'userName',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.sync({ force: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const u1 = await User.repository.create({
 | 
				
			||||||
 | 
					      values: { name: 'u1' },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const UserPostRepository = new HasManyRepository(User, 'posts', u1.get('name') as string);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p1 = await UserPostRepository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        title: 't1',
 | 
				
			||||||
 | 
					        status: 'published',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const p2 = await UserPostRepository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        title: 't2',
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await UserPostRepository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: p1.title,
 | 
				
			||||||
 | 
					      filter: { status: 'draft' },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await UserPostRepository.count()).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await UserPostRepository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: p1.title,
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        status: 'published',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      await UserPostRepository.findOne({
 | 
				
			||||||
 | 
					        filterByTk: p1.title,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    ).toBeNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      await UserPostRepository.findOne({
 | 
				
			||||||
 | 
					        filterByTk: p2.id,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    ).not.toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('has many repository', () => {
 | 
					describe('has many repository', () => {
 | 
				
			||||||
  let db;
 | 
					  let db;
 | 
				
			||||||
  let User;
 | 
					  let User;
 | 
				
			||||||
@ -190,7 +300,7 @@ describe('has many repository', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await UserPostRepository.destroy({
 | 
					    await UserPostRepository.destroy({
 | 
				
			||||||
      filterByPk: p1.id,
 | 
					      filterByTk: p1.id,
 | 
				
			||||||
      filter: {
 | 
					      filter: {
 | 
				
			||||||
        status: 'draft',
 | 
					        status: 'draft',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -199,7 +309,7 @@ describe('has many repository', () => {
 | 
				
			|||||||
    expect(await UserPostRepository.count()).toEqual(2);
 | 
					    expect(await UserPostRepository.count()).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await UserPostRepository.destroy({
 | 
					    await UserPostRepository.destroy({
 | 
				
			||||||
      filterByPk: p1.id,
 | 
					      filterByTk: p1.id,
 | 
				
			||||||
      filter: {
 | 
					      filter: {
 | 
				
			||||||
        status: 'published',
 | 
					        status: 'published',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -207,13 +317,13 @@ describe('has many repository', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(
 | 
					    expect(
 | 
				
			||||||
      await UserPostRepository.findOne({
 | 
					      await UserPostRepository.findOne({
 | 
				
			||||||
        filterByPk: p1.id,
 | 
					        filterByTk: p1.id,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    ).toBeNull();
 | 
					    ).toBeNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(
 | 
					    expect(
 | 
				
			||||||
      await UserPostRepository.findOne({
 | 
					      await UserPostRepository.findOne({
 | 
				
			||||||
        filterByPk: p2.id,
 | 
					        filterByTk: p2.id,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    ).not.toBeNull();
 | 
					    ).not.toBeNull();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,45 @@ import { Collection } from '../collection';
 | 
				
			|||||||
import { Database } from '../database';
 | 
					import { Database } from '../database';
 | 
				
			||||||
import { mockDatabase } from './';
 | 
					import { mockDatabase } from './';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('find by targetKey', function () {
 | 
				
			||||||
 | 
					  it('can filter by target key', async () => {
 | 
				
			||||||
 | 
					    const db = mockDatabase({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const User = db.collection({
 | 
				
			||||||
 | 
					      name: 'users',
 | 
				
			||||||
 | 
					      filterTargetKey: 'name',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          type: 'string',
 | 
				
			||||||
 | 
					          name: 'name',
 | 
				
			||||||
 | 
					          unique: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.sync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await User.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'user1',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await User.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'user2',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const user2 = await User.repository.findOne({
 | 
				
			||||||
 | 
					      filterByTk: 'user2',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(user2.get('name')).toEqual('user2');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('repository.find', () => {
 | 
					describe('repository.find', () => {
 | 
				
			||||||
  let db: Database;
 | 
					  let db: Database;
 | 
				
			||||||
  let User: Collection;
 | 
					  let User: Collection;
 | 
				
			||||||
@ -116,7 +155,7 @@ describe('repository.find', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const result = await Test.repository.findOne({
 | 
					    const result = await Test.repository.findOne({
 | 
				
			||||||
      filterByPk: <number>t1.get('id'),
 | 
					      filterByTk: <number>t1.get('id'),
 | 
				
			||||||
      filter: {
 | 
					      filter: {
 | 
				
			||||||
        status: 'published',
 | 
					        status: 'published',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -236,7 +275,7 @@ describe('repository.update', () => {
 | 
				
			|||||||
      name: 'user1',
 | 
					      name: 'user1',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await User.repository.update({
 | 
					    await User.repository.update({
 | 
				
			||||||
      filterByPk: user.id,
 | 
					      filterByTk: user.id,
 | 
				
			||||||
      values: {
 | 
					      values: {
 | 
				
			||||||
        name: 'user11',
 | 
					        name: 'user11',
 | 
				
			||||||
        posts: [{ name: 'post1' }],
 | 
					        posts: [{ name: 'post1' }],
 | 
				
			||||||
@ -263,28 +302,25 @@ describe('repository.update', () => {
 | 
				
			|||||||
  it('update2', async () => {
 | 
					  it('update2', async () => {
 | 
				
			||||||
    const user = await User.model.create<any>({
 | 
					    const user = await User.model.create<any>({
 | 
				
			||||||
      name: 'user1',
 | 
					      name: 'user1',
 | 
				
			||||||
      posts: [{ name: 'post1' }],
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const user2 = await User.model.create<any>({
 | 
				
			||||||
 | 
					      name: 'user2',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await User.repository.update({
 | 
					    await User.repository.update({
 | 
				
			||||||
      filterByPk: user.id,
 | 
					      filterByTk: user.id,
 | 
				
			||||||
      values: {
 | 
					      values: {
 | 
				
			||||||
        name: 'user11',
 | 
					        name: 'user11',
 | 
				
			||||||
        posts: [{ name: 'post1' }],
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const updated = await User.model.findByPk(user.id);
 | 
					    const updated = await User.model.findByPk(user.id);
 | 
				
			||||||
    expect(updated).toMatchObject({
 | 
					
 | 
				
			||||||
      name: 'user11',
 | 
					    expect(updated.get('name')).toEqual('user11');
 | 
				
			||||||
    });
 | 
					
 | 
				
			||||||
    const post = await Post.model.findOne({
 | 
					    const u2 = await User.model.findByPk(user2.id);
 | 
				
			||||||
      where: {
 | 
					    expect(u2.get('name')).toEqual('user2');
 | 
				
			||||||
        name: 'post1',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    expect(post).toMatchObject({
 | 
					 | 
				
			||||||
      name: 'post1',
 | 
					 | 
				
			||||||
      userId: user.id,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,80 @@
 | 
				
			|||||||
import { mockDatabase } from '../index';
 | 
					import { mockDatabase } from '../index';
 | 
				
			||||||
import { Collection } from '../../collection';
 | 
					import { Collection } from '../../collection';
 | 
				
			||||||
 | 
					import { Database } from '@nocobase/database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('destroy with targetKey', function () {
 | 
				
			||||||
 | 
					  let db: Database;
 | 
				
			||||||
 | 
					  let User: Collection;
 | 
				
			||||||
 | 
					  let u1;
 | 
				
			||||||
 | 
					  let u2;
 | 
				
			||||||
 | 
					  afterEach(async () => {
 | 
				
			||||||
 | 
					    await db.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    db = mockDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    User = db.collection({
 | 
				
			||||||
 | 
					      name: 'users',
 | 
				
			||||||
 | 
					      autoGenId: false,
 | 
				
			||||||
 | 
					      fields: [
 | 
				
			||||||
 | 
					        { type: 'string', name: 'name', primaryKey: true },
 | 
				
			||||||
 | 
					        { type: 'string', name: 'status' },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await db.sync({
 | 
				
			||||||
 | 
					      force: true,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    u1 = await User.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'u1',
 | 
				
			||||||
 | 
					        status: 'published',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    u2 = await User.repository.create({
 | 
				
			||||||
 | 
					      values: {
 | 
				
			||||||
 | 
					        name: 'u2',
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should destroy all', async () => {
 | 
				
			||||||
 | 
					    expect(await User.repository.count()).toEqual(2);
 | 
				
			||||||
 | 
					    await User.repository.destroy({ truncate: true });
 | 
				
			||||||
 | 
					    expect(await User.repository.count()).toEqual(0);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should destroy by target key', async () => {
 | 
				
			||||||
 | 
					    await User.repository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: 'u2',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await User.repository.count()).toEqual(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should destroy by target key and filter', async () => {
 | 
				
			||||||
 | 
					    await User.repository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: 'u1',
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(await User.repository.count()).toEqual(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await User.repository.destroy({
 | 
				
			||||||
 | 
					      filterByTk: 'u2',
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        status: 'draft',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(await User.repository.count()).toEqual(1);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('destroy', () => {
 | 
					describe('destroy', () => {
 | 
				
			||||||
  let db;
 | 
					  let db;
 | 
				
			||||||
@ -36,7 +111,7 @@ describe('destroy', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await Post.repository.destroy({
 | 
					    await Post.repository.destroy({
 | 
				
			||||||
      filterByPk: p1.get('id') as number,
 | 
					      filterByTk: p1.get('id') as number,
 | 
				
			||||||
      filter: {
 | 
					      filter: {
 | 
				
			||||||
        status: 'draft',
 | 
					        status: 'draft',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ export type RepositoryType = typeof Repository;
 | 
				
			|||||||
export interface CollectionOptions extends Omit<ModelOptions, 'name'> {
 | 
					export interface CollectionOptions extends Omit<ModelOptions, 'name'> {
 | 
				
			||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
  tableName?: string;
 | 
					  tableName?: string;
 | 
				
			||||||
 | 
					  filterTargetKey?: string;
 | 
				
			||||||
  fields?: FieldOptions[];
 | 
					  fields?: FieldOptions[];
 | 
				
			||||||
  model?: string | ModelCtor<Model>;
 | 
					  model?: string | ModelCtor<Model>;
 | 
				
			||||||
  repository?: string | RepositoryType;
 | 
					  repository?: string | RepositoryType;
 | 
				
			||||||
@ -37,6 +38,10 @@ export class Collection<
 | 
				
			|||||||
  model: ModelCtor<Model<TModelAttributes, TCreationAttributes>>;
 | 
					  model: ModelCtor<Model<TModelAttributes, TCreationAttributes>>;
 | 
				
			||||||
  repository: Repository<TModelAttributes, TCreationAttributes>;
 | 
					  repository: Repository<TModelAttributes, TCreationAttributes>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get filterTargetKey() {
 | 
				
			||||||
 | 
					    return lodash.get(this.options, 'filterTargetKey', this.model.primaryKeyAttribute);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get name() {
 | 
					  get name() {
 | 
				
			||||||
    return this.options.name;
 | 
					    return this.options.name;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -54,7 +59,7 @@ export class Collection<
 | 
				
			|||||||
  private sequelizeModelOptions() {
 | 
					  private sequelizeModelOptions() {
 | 
				
			||||||
    const { name, tableName } = this.options;
 | 
					    const { name, tableName } = this.options;
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      ..._.omit(this.options, ['name', 'fields']),
 | 
					      ..._.omit(this.options, ['name', 'fields', 'model']),
 | 
				
			||||||
      modelName: name,
 | 
					      modelName: name,
 | 
				
			||||||
      sequelize: this.context.database.sequelize,
 | 
					      sequelize: this.context.database.sequelize,
 | 
				
			||||||
      tableName: tableName || name,
 | 
					      tableName: tableName || name,
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,10 @@ export abstract class RelationField extends Field {
 | 
				
			|||||||
    return this.options.sourceKey;
 | 
					    return this.options.sourceKey;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get targetKey() {
 | 
				
			||||||
 | 
					    return this.options.targetKey || this.TargetModel.primaryKeyAttribute;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * get target model from database by it's name
 | 
					   * get target model from database by it's name
 | 
				
			||||||
   * @constructor
 | 
					   * @constructor
 | 
				
			||||||
 | 
				
			|||||||
@ -3,20 +3,30 @@ import _ from 'lodash';
 | 
				
			|||||||
import { flatten, unflatten } from 'flat';
 | 
					import { flatten, unflatten } from 'flat';
 | 
				
			||||||
import { Database } from './database';
 | 
					import { Database } from './database';
 | 
				
			||||||
import lodash from 'lodash';
 | 
					import lodash from 'lodash';
 | 
				
			||||||
 | 
					import { Collection } from './collection';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const debug = require('debug')('noco-database');
 | 
					const debug = require('debug')('noco-database');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FilterType = any;
 | 
					type FilterType = any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FilterParserContext {
 | 
				
			||||||
 | 
					  collection: Collection;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class FilterParser {
 | 
					export default class FilterParser {
 | 
				
			||||||
 | 
					  collection: Collection;
 | 
				
			||||||
  database: Database;
 | 
					  database: Database;
 | 
				
			||||||
  model: ModelCtor<Model>;
 | 
					  model: ModelCtor<Model>;
 | 
				
			||||||
  filter: FilterType;
 | 
					  filter: FilterType;
 | 
				
			||||||
 | 
					  context: FilterParserContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(model: ModelCtor<any>, database: Database, filter: FilterType) {
 | 
					  constructor(filter: FilterType, context: FilterParserContext) {
 | 
				
			||||||
    this.model = model;
 | 
					    const { collection } = context;
 | 
				
			||||||
 | 
					    this.collection = collection;
 | 
				
			||||||
 | 
					    this.context = context;
 | 
				
			||||||
 | 
					    this.model = collection.model;
 | 
				
			||||||
    this.filter = this.prepareFilter(filter);
 | 
					    this.filter = this.prepareFilter(filter);
 | 
				
			||||||
    this.database = database;
 | 
					    this.database = collection.context.database;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  prepareFilter(filter: FilterType) {
 | 
					  prepareFilter(filter: FilterType) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,20 +2,32 @@ import { Appends, Except, FindOptions } from './repository';
 | 
				
			|||||||
import FilterParser from './filter-parser';
 | 
					import FilterParser from './filter-parser';
 | 
				
			||||||
import { FindAttributeOptions, ModelCtor, Op } from 'sequelize';
 | 
					import { FindAttributeOptions, ModelCtor, Op } from 'sequelize';
 | 
				
			||||||
import { Database } from './database';
 | 
					import { Database } from './database';
 | 
				
			||||||
 | 
					import { Collection } from './collection';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const debug = require('debug')('noco-database');
 | 
					const debug = require('debug')('noco-database');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface OptionsParserContext {
 | 
				
			||||||
 | 
					  collection: Collection;
 | 
				
			||||||
 | 
					  targetKey?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class OptionsParser {
 | 
					export class OptionsParser {
 | 
				
			||||||
  options: FindOptions;
 | 
					  options: FindOptions;
 | 
				
			||||||
  database: Database;
 | 
					  database: Database;
 | 
				
			||||||
 | 
					  collection: Collection;
 | 
				
			||||||
  model: ModelCtor<any>;
 | 
					  model: ModelCtor<any>;
 | 
				
			||||||
  filterParser: FilterParser;
 | 
					  filterParser: FilterParser;
 | 
				
			||||||
 | 
					  context: OptionsParserContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(model: ModelCtor<any>, database: Database, options: FindOptions) {
 | 
					  constructor(options: FindOptions, context: OptionsParserContext) {
 | 
				
			||||||
    this.model = model;
 | 
					    const { collection } = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.collection = collection;
 | 
				
			||||||
 | 
					    this.model = collection.model;
 | 
				
			||||||
    this.options = options;
 | 
					    this.options = options;
 | 
				
			||||||
    this.database = database;
 | 
					    this.database = collection.context.database;
 | 
				
			||||||
    this.filterParser = new FilterParser(model, this.database, options?.filter);
 | 
					    this.filterParser = new FilterParser(options?.filter, { collection });
 | 
				
			||||||
 | 
					    this.context = context;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isAssociation(key: string) {
 | 
					  isAssociation(key: string) {
 | 
				
			||||||
@ -26,27 +38,15 @@ export class OptionsParser {
 | 
				
			|||||||
    return this.isAssociation(path.split('.')[0]);
 | 
					    return this.isAssociation(path.split('.')[0]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  parseFilterByPk() {
 | 
					 | 
				
			||||||
    if (this.options?.filterByPk) {
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
        where: {
 | 
					 | 
				
			||||||
          [this.model.primaryKeyAttribute]: this.options.filterByPk,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  toSequelizeParams() {
 | 
					  toSequelizeParams() {
 | 
				
			||||||
    const queryParams = this.filterParser.toSequelizeParams();
 | 
					    const queryParams = this.filterParser.toSequelizeParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.options?.filterByPk) {
 | 
					    if (this.options?.filterByTk) {
 | 
				
			||||||
      queryParams.where = {
 | 
					      queryParams.where = {
 | 
				
			||||||
        [Op.and]: [
 | 
					        [Op.and]: [
 | 
				
			||||||
          queryParams.where,
 | 
					          queryParams.where,
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            [this.model.primaryKeyAttribute]: this.options.filterByPk,
 | 
					            [this.context.targetKey || this.collection.filterTargetKey]: this.options.filterByTk,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { BelongsToMany, Model, Op, Transaction } from 'sequelize';
 | 
					import { BelongsToMany, Model, Op, Transaction } from 'sequelize';
 | 
				
			||||||
import { updateThroughTableValue } from '../update-associations';
 | 
					import { updateThroughTableValue } from '../update-associations';
 | 
				
			||||||
import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository';
 | 
					import { FindAndCountOptions, FindOneOptions, MultipleRelationRepository } from './multiple-relation-repository';
 | 
				
			||||||
import { CreateOptions, DestroyOptions, FindOptions, PrimaryKey, UpdateOptions } from '../repository';
 | 
					import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
 | 
				
			||||||
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
 | 
					import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
 | 
				
			||||||
import lodash from 'lodash';
 | 
					import lodash from 'lodash';
 | 
				
			||||||
import { transaction } from './relation-repository';
 | 
					import { transaction } from './relation-repository';
 | 
				
			||||||
@ -19,12 +19,12 @@ interface IBelongsToManyRepository<M extends Model> {
 | 
				
			|||||||
  // 删除
 | 
					  // 删除
 | 
				
			||||||
  destroy(options?: number | string | number[] | string[] | DestroyOptions): Promise<Boolean>;
 | 
					  destroy(options?: number | string | number[] | string[] | DestroyOptions): Promise<Boolean>;
 | 
				
			||||||
  // 建立关联
 | 
					  // 建立关联
 | 
				
			||||||
  set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
  // 附加关联,存在中间表数据
 | 
					  // 附加关联,存在中间表数据
 | 
				
			||||||
  add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
  // 移除关联
 | 
					  // 移除关联
 | 
				
			||||||
  remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
  toggle(options: PrimaryKey | { pk?: PrimaryKey; transaction?: Transaction }): Promise<void>;
 | 
					  toggle(options: TargetKey | { pk?: TargetKey; transaction?: Transaction }): Promise<void>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class BelongsToManyRepository extends MultipleRelationRepository implements IBelongsToManyRepository<any> {
 | 
					export class BelongsToManyRepository extends MultipleRelationRepository implements IBelongsToManyRepository<any> {
 | 
				
			||||||
@ -48,22 +48,22 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @transaction((args, transaction) => {
 | 
					  @transaction((args, transaction) => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      filterByPk: args[0],
 | 
					      filterByTk: args[0],
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async destroy(options?: PrimaryKey | PrimaryKey[] | DestroyOptions): Promise<Boolean> {
 | 
					  async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise<Boolean> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
    const association = <BelongsToMany>this.association;
 | 
					    const association = <BelongsToMany>this.association;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const instancesToIds = (instances) => {
 | 
					    const instancesToIds = (instances) => {
 | 
				
			||||||
      return instances.map((instance) => instance.get(this.target.primaryKeyAttribute));
 | 
					      return instances.map((instance) => instance.get(this.targetKey()));
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Through Table
 | 
					    // Through Table
 | 
				
			||||||
    const throughTableWhere: Array<any> = [
 | 
					    const throughTableWhere: Array<any> = [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        [association.foreignKey]: this.sourceId,
 | 
					        [association.foreignKey]: this.sourceKeyValue,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,12 +78,12 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
      ids = instancesToIds(instances);
 | 
					      ids = instancesToIds(instances);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options && options['filterByPk']) {
 | 
					    if (options && options['filterByTk']) {
 | 
				
			||||||
      const instances = (<any>this.association).toInstanceArray(options['filterByPk']);
 | 
					      const instances = (<any>this.association).toInstanceArray(options['filterByTk']);
 | 
				
			||||||
      ids = ids ? lodash.intersection(ids, instancesToIds(instances)) : instancesToIds(instances);
 | 
					      ids = ids ? lodash.intersection(ids, instancesToIds(instances)) : instancesToIds(instances);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options && !options['filterByPk'] && !options['filter']) {
 | 
					    if (options && !options['filterByTk'] && !options['filter']) {
 | 
				
			||||||
      const sourceModel = await this.getSourceModel(transaction);
 | 
					      const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const instances = await sourceModel[this.accessors().get]({
 | 
					      const instances = await sourceModel[this.accessors().get]({
 | 
				
			||||||
@ -105,9 +105,9 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.target.destroy({
 | 
					    await this.targetModel.destroy({
 | 
				
			||||||
      where: {
 | 
					      where: {
 | 
				
			||||||
        [this.target.primaryKeyAttribute]: {
 | 
					        [this.targetKey()]: {
 | 
				
			||||||
          [Op.in]: ids,
 | 
					          [Op.in]: ids,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -119,14 +119,9 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  protected async setTargets(
 | 
					  protected async setTargets(
 | 
				
			||||||
    call: 'add' | 'set',
 | 
					    call: 'add' | 'set',
 | 
				
			||||||
    options:
 | 
					    options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
 | 
				
			||||||
      | PrimaryKey
 | 
					 | 
				
			||||||
      | PrimaryKey[]
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues[]
 | 
					 | 
				
			||||||
      | AssociatedOptions,
 | 
					 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    let handleKeys: PrimaryKey[] | PrimaryKeyWithThroughValues[];
 | 
					    let handleKeys: TargetKey[] | PrimaryKeyWithThroughValues[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const transaction = await this.getTransaction(options, false);
 | 
					    const transaction = await this.getTransaction(options, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -135,12 +130,12 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (lodash.isString(options) || lodash.isNumber(options)) {
 | 
					    if (lodash.isString(options) || lodash.isNumber(options)) {
 | 
				
			||||||
      handleKeys = [<PrimaryKey>options];
 | 
					      handleKeys = [<TargetKey>options];
 | 
				
			||||||
    } // if it is type primaryKeyWithThroughValues
 | 
					    } // if it is type primaryKeyWithThroughValues
 | 
				
			||||||
    else if (lodash.isArray(options) && options.length == 2 && lodash.isPlainObject(options[0][1])) {
 | 
					    else if (lodash.isArray(options) && options.length == 2 && lodash.isPlainObject(options[0][1])) {
 | 
				
			||||||
      handleKeys = [<PrimaryKeyWithThroughValues>options];
 | 
					      handleKeys = [<PrimaryKeyWithThroughValues>options];
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      handleKeys = <PrimaryKey[] | PrimaryKeyWithThroughValues[]>options;
 | 
					      handleKeys = <TargetKey[] | PrimaryKeyWithThroughValues[]>options;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
@ -160,7 +155,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    for (const [id, throughValues] of Object.entries(setObj)) {
 | 
					    for (const [id, throughValues] of Object.entries(setObj)) {
 | 
				
			||||||
      if (typeof throughValues === 'object') {
 | 
					      if (typeof throughValues === 'object') {
 | 
				
			||||||
        const instance = await this.target.findByPk(id, {
 | 
					        const instance = await this.targetModel.findByPk(id, {
 | 
				
			||||||
          transaction,
 | 
					          transaction,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        await updateThroughTableValue(instance, this.throughName(), throughValues, sourceModel, transaction);
 | 
					        await updateThroughTableValue(instance, this.throughName(), throughValues, sourceModel, transaction);
 | 
				
			||||||
@ -175,12 +170,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async add(
 | 
					  async add(
 | 
				
			||||||
    options:
 | 
					    options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
 | 
				
			||||||
      | PrimaryKey
 | 
					 | 
				
			||||||
      | PrimaryKey[]
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues[]
 | 
					 | 
				
			||||||
      | AssociatedOptions,
 | 
					 | 
				
			||||||
  ): Promise<void> {
 | 
					  ): Promise<void> {
 | 
				
			||||||
    await this.setTargets('add', options);
 | 
					    await this.setTargets('add', options);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -192,12 +182,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async set(
 | 
					  async set(
 | 
				
			||||||
    options:
 | 
					    options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
 | 
				
			||||||
      | PrimaryKey
 | 
					 | 
				
			||||||
      | PrimaryKey[]
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues
 | 
					 | 
				
			||||||
      | PrimaryKeyWithThroughValues[]
 | 
					 | 
				
			||||||
      | AssociatedOptions,
 | 
					 | 
				
			||||||
  ): Promise<void> {
 | 
					  ): Promise<void> {
 | 
				
			||||||
    await this.setTargets('set', options);
 | 
					    await this.setTargets('set', options);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -208,7 +193,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository implemen
 | 
				
			|||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async toggle(options: PrimaryKey | { pk?: PrimaryKey; transaction?: Transaction }): Promise<void> {
 | 
					  async toggle(options: TargetKey | { pk?: TargetKey; transaction?: Transaction }): Promise<void> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
    const has = await sourceModel[this.accessors().hasSingle](options['pk'], {
 | 
					    const has = await sourceModel[this.accessors().hasSingle](options['pk'], {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,9 @@ import {
 | 
				
			|||||||
  FindOneOptions,
 | 
					  FindOneOptions,
 | 
				
			||||||
  MultipleRelationRepository,
 | 
					  MultipleRelationRepository,
 | 
				
			||||||
} from './multiple-relation-repository';
 | 
					} from './multiple-relation-repository';
 | 
				
			||||||
import { CreateOptions, DestroyOptions, FindOptions, PK, PrimaryKey, UpdateOptions } from '../repository';
 | 
					import { CreateOptions, DestroyOptions, FindOptions, TK, TargetKey, UpdateOptions } from '../repository';
 | 
				
			||||||
import { transaction } from './relation-repository';
 | 
					import { transaction } from './relation-repository';
 | 
				
			||||||
 | 
					import lodash from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IHasManyRepository<M extends Model> {
 | 
					interface IHasManyRepository<M extends Model> {
 | 
				
			||||||
  find(options?: FindOptions): Promise<M>;
 | 
					  find(options?: FindOptions): Promise<M>;
 | 
				
			||||||
@ -18,30 +19,30 @@ interface IHasManyRepository<M extends Model> {
 | 
				
			|||||||
  // 更新
 | 
					  // 更新
 | 
				
			||||||
  update(options?: UpdateOptions): Promise<M>;
 | 
					  update(options?: UpdateOptions): Promise<M>;
 | 
				
			||||||
  // 删除
 | 
					  // 删除
 | 
				
			||||||
  destroy(options?: PK | DestroyOptions): Promise<Boolean>;
 | 
					  destroy(options?: TK | DestroyOptions): Promise<Boolean>;
 | 
				
			||||||
  // 建立关联
 | 
					  // 建立关联
 | 
				
			||||||
  set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
  // 附加关联
 | 
					  // 附加关联
 | 
				
			||||||
  add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
  // 移除关联
 | 
					  // 移除关联
 | 
				
			||||||
  remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void>;
 | 
					  remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class HasManyRepository extends MultipleRelationRepository implements IHasManyRepository<any> {
 | 
					export class HasManyRepository extends MultipleRelationRepository implements IHasManyRepository<any> {
 | 
				
			||||||
  @transaction((args, transaction) => {
 | 
					  @transaction((args, transaction) => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      filterByPk: args[0],
 | 
					      filterByTk: args[0],
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
 | 
					  async destroy(options?: TK | DestroyOptions): Promise<Boolean> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const where = [
 | 
					    const where = [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        [this.association.foreignKey]: sourceModel.get(this.source.model.primaryKeyAttribute),
 | 
					        [this.association.foreignKey]: sourceModel.get((this.association as any).sourceKey),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,21 +55,18 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      where.push(filterResult.where);
 | 
					      where.push(filterResult.where);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (options && options['filterByPk']) {
 | 
					
 | 
				
			||||||
      if (typeof options === 'object' && options['filterByPk']) {
 | 
					    if (options && options['filterByTk']) {
 | 
				
			||||||
        options = options['filterByPk'];
 | 
					      if (typeof options === 'object' && options['filterByTk']) {
 | 
				
			||||||
 | 
					        options = options['filterByTk'];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const targetInstances = (<any>this.association).toInstanceArray(options);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      where.push({
 | 
					      where.push({
 | 
				
			||||||
        [this.target.primaryKeyAttribute]: targetInstances.map((targetInstance) =>
 | 
					        [this.targetKey()]: options,
 | 
				
			||||||
          targetInstance.get(this.target.primaryKeyAttribute),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.target.destroy({
 | 
					    await this.targetModel.destroy({
 | 
				
			||||||
      where: {
 | 
					      where: {
 | 
				
			||||||
        [Op.and]: where,
 | 
					        [Op.and]: where,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -95,7 +93,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
 | 
				
			|||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async set(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
 | 
					  async set(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
@ -111,7 +109,7 @@ export class HasManyRepository extends MultipleRelationRepository implements IHa
 | 
				
			|||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async add(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
 | 
					  async add(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { RelationRepository, transaction } from './relation-repository';
 | 
					import { RelationRepository, transaction } from './relation-repository';
 | 
				
			||||||
import { omit } from 'lodash';
 | 
					import lodash, { omit } from 'lodash';
 | 
				
			||||||
import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
 | 
					import { MultiAssociationAccessors, Op, Sequelize, Transaction } from 'sequelize';
 | 
				
			||||||
import { UpdateGuard } from '../update-guard';
 | 
					import { UpdateGuard } from '../update-guard';
 | 
				
			||||||
import { updateModelByValues } from '../update-associations';
 | 
					import { updateModelByValues } from '../update-associations';
 | 
				
			||||||
@ -8,20 +8,20 @@ import {
 | 
				
			|||||||
  CountOptions,
 | 
					  CountOptions,
 | 
				
			||||||
  DestroyOptions,
 | 
					  DestroyOptions,
 | 
				
			||||||
  Filter,
 | 
					  Filter,
 | 
				
			||||||
  FilterByPK,
 | 
					  FilterByTk,
 | 
				
			||||||
  FindOptions,
 | 
					  FindOptions,
 | 
				
			||||||
  PK,
 | 
					  TK,
 | 
				
			||||||
  PrimaryKey,
 | 
					  TargetKey,
 | 
				
			||||||
  TransactionAble,
 | 
					  TransactionAble,
 | 
				
			||||||
  UpdateOptions,
 | 
					  UpdateOptions,
 | 
				
			||||||
} from '../repository';
 | 
					} from '../repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FindAndCountOptions extends CommonFindOptions {}
 | 
					export interface FindAndCountOptions extends CommonFindOptions {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FindOneOptions extends CommonFindOptions, FilterByPK {}
 | 
					export interface FindOneOptions extends CommonFindOptions, FilterByTk {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AssociatedOptions extends TransactionAble {
 | 
					export interface AssociatedOptions extends TransactionAble {
 | 
				
			||||||
  pk?: PK;
 | 
					  tk?: TK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class MultipleRelationRepository extends RelationRepository {
 | 
					export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			||||||
@ -46,16 +46,16 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
        await sourceModel[getAccessor]({
 | 
					        await sourceModel[getAccessor]({
 | 
				
			||||||
          ...findOptions,
 | 
					          ...findOptions,
 | 
				
			||||||
          includeIgnoreAttributes: false,
 | 
					          includeIgnoreAttributes: false,
 | 
				
			||||||
          attributes: [this.target.primaryKeyAttribute],
 | 
					          attributes: [this.targetKey()],
 | 
				
			||||||
          group: `${this.target.name}.${this.target.primaryKeyAttribute}`,
 | 
					          group: `${this.targetModel.name}.${this.targetKey()}`,
 | 
				
			||||||
          transaction,
 | 
					          transaction,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      ).map((row) => row.get(this.target.primaryKeyAttribute));
 | 
					      ).map((row) => row.get(this.targetKey()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return await sourceModel[getAccessor]({
 | 
					      return await sourceModel[getAccessor]({
 | 
				
			||||||
        ...omit(findOptions, ['limit', 'offset']),
 | 
					        ...omit(findOptions, ['limit', 'offset']),
 | 
				
			||||||
        where: {
 | 
					        where: {
 | 
				
			||||||
          [this.target.primaryKeyAttribute]: {
 | 
					          [this.targetKey()]: {
 | 
				
			||||||
            [Op.in]: ids,
 | 
					            [Op.in]: ids,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -97,7 +97,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
        [
 | 
					        [
 | 
				
			||||||
          Sequelize.fn(
 | 
					          Sequelize.fn(
 | 
				
			||||||
            'COUNT',
 | 
					            'COUNT',
 | 
				
			||||||
            Sequelize.fn('DISTINCT', Sequelize.col(`${this.target.name}.${this.target.primaryKeyAttribute}`)),
 | 
					            Sequelize.fn('DISTINCT', Sequelize.col(`${this.targetModel.name}.${this.targetKey()}`)),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          'count',
 | 
					          'count',
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -122,7 +122,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async remove(options: PrimaryKey | PrimaryKey[] | AssociatedOptions): Promise<void> {
 | 
					  async remove(options: TargetKey | TargetKey[] | AssociatedOptions): Promise<void> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
    let handleKeys = options['pk'];
 | 
					    let handleKeys = options['pk'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -141,7 +141,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
  async update(options?: UpdateOptions): Promise<any> {
 | 
					  async update(options?: UpdateOptions): Promise<any> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const guard = UpdateGuard.fromOptions(this.target, options);
 | 
					    const guard = UpdateGuard.fromOptions(this.targetModel, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const values = guard.sanitize(options.values);
 | 
					    const values = guard.sanitize(options.values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -153,7 +153,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
      await updateModelByValues(instance, values, {
 | 
					      await updateModelByValues(instance, values, {
 | 
				
			||||||
        ...options,
 | 
					        ...options,
 | 
				
			||||||
        sanitized: true,
 | 
					        sanitized: true,
 | 
				
			||||||
        sourceModel: this.sourceModel,
 | 
					        sourceModel: this.sourceInstance,
 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -161,7 +161,7 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
    return instances;
 | 
					    return instances;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async destroy(options?: PK | DestroyOptions): Promise<Boolean> {
 | 
					  async destroy(options?: TK | DestroyOptions): Promise<Boolean> {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -170,8 +170,9 @@ export abstract class MultipleRelationRepository extends RelationRepository {
 | 
				
			|||||||
      filter: filter,
 | 
					      filter: filter,
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return await this.destroy({
 | 
					    return await this.destroy({
 | 
				
			||||||
      filterByPk: instances.map((instance) => instance[this.target.primaryKeyAttribute]),
 | 
					      filterByTk: instances.map((instance) => instance.get(this.targetCollection.filterTargetKey)),
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -7,24 +7,36 @@ import { UpdateGuard } from '../update-guard';
 | 
				
			|||||||
import { updateAssociations } from '../update-associations';
 | 
					import { updateAssociations } from '../update-associations';
 | 
				
			||||||
import lodash from 'lodash';
 | 
					import lodash from 'lodash';
 | 
				
			||||||
import { transactionWrapperBuilder } from '../transaction-decorator';
 | 
					import { transactionWrapperBuilder } from '../transaction-decorator';
 | 
				
			||||||
 | 
					import { Field, RelationField } from '@nocobase/database';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const transaction = transactionWrapperBuilder(function () {
 | 
					export const transaction = transactionWrapperBuilder(function () {
 | 
				
			||||||
  return this.source.model.sequelize.transaction();
 | 
					  return this.sourceCollection.model.sequelize.transaction();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class RelationRepository {
 | 
					export abstract class RelationRepository {
 | 
				
			||||||
  source: Collection;
 | 
					  sourceCollection: Collection;
 | 
				
			||||||
  association: Association;
 | 
					  association: Association;
 | 
				
			||||||
  target: ModelCtor<any>;
 | 
					  targetModel: ModelCtor<any>;
 | 
				
			||||||
  sourceId: string | number;
 | 
					  targetCollection: Collection;
 | 
				
			||||||
  sourceModel: Model;
 | 
					  associationName: string;
 | 
				
			||||||
 | 
					  associationField: RelationField;
 | 
				
			||||||
 | 
					  sourceKeyValue: string | number;
 | 
				
			||||||
 | 
					  sourceInstance: Model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(source: Collection, association: string, sourceId: string | number) {
 | 
					  constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number) {
 | 
				
			||||||
    this.source = source;
 | 
					    this.sourceCollection = sourceCollection;
 | 
				
			||||||
    this.sourceId = sourceId;
 | 
					    this.sourceKeyValue = sourceKeyValue;
 | 
				
			||||||
    this.association = this.source.model.associations[association];
 | 
					    this.associationName = association;
 | 
				
			||||||
 | 
					    this.association = this.sourceCollection.model.associations[association];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.target = this.association.target;
 | 
					    this.associationField = this.sourceCollection.getField(association);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.targetModel = this.association.target;
 | 
				
			||||||
 | 
					    this.targetCollection = this.sourceCollection.context.database.modelCollection.get(this.targetModel);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  targetKey() {
 | 
				
			||||||
 | 
					    return this.associationField.targetKey;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected accessors() {
 | 
					  protected accessors() {
 | 
				
			||||||
@ -34,7 +46,7 @@ export abstract class RelationRepository {
 | 
				
			|||||||
  async create(options?: CreateOptions): Promise<any> {
 | 
					  async create(options?: CreateOptions): Promise<any> {
 | 
				
			||||||
    const createAccessor = this.accessors().create;
 | 
					    const createAccessor = this.accessors().create;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const guard = UpdateGuard.fromOptions(this.target, options);
 | 
					    const guard = UpdateGuard.fromOptions(this.targetModel, options);
 | 
				
			||||||
    const values = options.values;
 | 
					    const values = options.values;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel();
 | 
					    const sourceModel = await this.getSourceModel();
 | 
				
			||||||
@ -47,23 +59,30 @@ export abstract class RelationRepository {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getSourceModel(transaction?: any) {
 | 
					  async getSourceModel(transaction?: any) {
 | 
				
			||||||
    if (!this.sourceModel) {
 | 
					    if (!this.sourceInstance) {
 | 
				
			||||||
      this.sourceModel = await this.source.model.findByPk(this.sourceId, {
 | 
					      this.sourceInstance = await this.sourceCollection.model.findOne({
 | 
				
			||||||
        transaction,
 | 
					        where: {
 | 
				
			||||||
 | 
					          [this.associationField.sourceKey]: this.sourceKeyValue,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.sourceModel;
 | 
					    return this.sourceInstance;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected buildQueryOptions(options: FindOptions) {
 | 
					  protected buildQueryOptions(options: FindOptions) {
 | 
				
			||||||
    const parser = new OptionsParser(this.target, this.source.context.database, options);
 | 
					    const parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: this.targetCollection,
 | 
				
			||||||
 | 
					      targetKey: this.targetKey(),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    const params = parser.toSequelizeParams();
 | 
					    const params = parser.toSequelizeParams();
 | 
				
			||||||
    return { ...options, ...params };
 | 
					    return { ...options, ...params };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected parseFilter(filter: Filter) {
 | 
					  protected parseFilter(filter: Filter) {
 | 
				
			||||||
    const parser = new FilterParser(this.target, this.source.context.database, filter);
 | 
					    const parser = new FilterParser(filter, {
 | 
				
			||||||
 | 
					      collection: this.targetCollection,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    return parser.toSequelizeParams();
 | 
					    return parser.toSequelizeParams();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,7 +92,7 @@ export abstract class RelationRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (autoGen) {
 | 
					    if (autoGen) {
 | 
				
			||||||
      return await this.source.model.sequelize.transaction();
 | 
					      return await this.sourceCollection.model.sequelize.transaction();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { RelationRepository, transaction } from './relation-repository';
 | 
				
			|||||||
import { Model, SingleAssociationAccessors } from 'sequelize';
 | 
					import { Model, SingleAssociationAccessors } from 'sequelize';
 | 
				
			||||||
import { updateModelByValues } from '../update-associations';
 | 
					import { updateModelByValues } from '../update-associations';
 | 
				
			||||||
import lodash from 'lodash';
 | 
					import lodash from 'lodash';
 | 
				
			||||||
import { Appends, Except, Fields, Filter, PrimaryKey, TransactionAble, UpdateOptions } from '../repository';
 | 
					import { Appends, Except, Fields, Filter, TargetKey, TransactionAble, UpdateOptions } from '../repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SingleRelationFindOption extends TransactionAble {
 | 
					export interface SingleRelationFindOption extends TransactionAble {
 | 
				
			||||||
  fields?: Fields;
 | 
					  fields?: Fields;
 | 
				
			||||||
@ -12,7 +12,7 @@ export interface SingleRelationFindOption extends TransactionAble {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SetOption extends TransactionAble {
 | 
					interface SetOption extends TransactionAble {
 | 
				
			||||||
  pk?: PrimaryKey;
 | 
					  tk?: TargetKey;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class SingleRelationRepository extends RelationRepository {
 | 
					export abstract class SingleRelationRepository extends RelationRepository {
 | 
				
			||||||
@ -27,13 +27,13 @@ export abstract class SingleRelationRepository extends RelationRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @transaction((args, transaction) => {
 | 
					  @transaction((args, transaction) => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      pk: args[0],
 | 
					      tk: args[0],
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async set(options: PrimaryKey | SetOption): Promise<void> {
 | 
					  async set(options: TargetKey | SetOption): Promise<void> {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
    let handleKey = lodash.isPlainObject(options) ? (<SetOption>options).pk : options;
 | 
					    let handleKey = lodash.isPlainObject(options) ? (<SetOption>options).tk : options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sourceModel = await this.getSourceModel(transaction);
 | 
					    const sourceModel = await this.getSourceModel(transaction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -43,8 +43,8 @@ export interface FilterAble {
 | 
				
			|||||||
  filter: Filter;
 | 
					  filter: Filter;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type PrimaryKey = string | number;
 | 
					export type TargetKey = string | number;
 | 
				
			||||||
export type PK = PrimaryKey | PrimaryKey[];
 | 
					export type TK = TargetKey | TargetKey[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Filter = any;
 | 
					export type Filter = any;
 | 
				
			||||||
export type Appends = string[];
 | 
					export type Appends = string[];
 | 
				
			||||||
@ -63,11 +63,11 @@ export interface CountOptions extends Omit<SequelizeCreateOptions, 'distinct' |
 | 
				
			|||||||
  filter?: Filter;
 | 
					  filter?: Filter;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FilterByPK {
 | 
					export interface FilterByTk {
 | 
				
			||||||
  filterByPk?: PrimaryKey;
 | 
					  filterByTk?: TargetKey;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FindOptions extends SequelizeFindOptions, CommonFindOptions, FilterByPK {}
 | 
					export interface FindOptions extends SequelizeFindOptions, CommonFindOptions, FilterByTk {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CommonFindOptions {
 | 
					export interface CommonFindOptions {
 | 
				
			||||||
  filter?: Filter;
 | 
					  filter?: Filter;
 | 
				
			||||||
@ -81,7 +81,7 @@ interface FindOneOptions extends FindOptions, CommonFindOptions {}
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface DestroyOptions extends SequelizeDestroyOptions {
 | 
					export interface DestroyOptions extends SequelizeDestroyOptions {
 | 
				
			||||||
  filter?: Filter;
 | 
					  filter?: Filter;
 | 
				
			||||||
  filterByPk?: PrimaryKey | PrimaryKey[];
 | 
					  filterByTk?: TargetKey | TargetKey[];
 | 
				
			||||||
  truncate?: boolean;
 | 
					  truncate?: boolean;
 | 
				
			||||||
  context?: any;
 | 
					  context?: any;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -110,7 +110,7 @@ export interface CreateOptions extends SequelizeCreateOptions {
 | 
				
			|||||||
export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
 | 
					export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
 | 
				
			||||||
  values: Values;
 | 
					  values: Values;
 | 
				
			||||||
  filter?: Filter;
 | 
					  filter?: Filter;
 | 
				
			||||||
  filterByPk?: PrimaryKey;
 | 
					  filterByTk?: TargetKey;
 | 
				
			||||||
  whitelist?: WhiteList;
 | 
					  whitelist?: WhiteList;
 | 
				
			||||||
  blacklist?: BlackList;
 | 
					  blacklist?: BlackList;
 | 
				
			||||||
  updateAssociationValues?: AssociationKeysToBeUpdate;
 | 
					  updateAssociationValues?: AssociationKeysToBeUpdate;
 | 
				
			||||||
@ -261,7 +261,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
   * Find By Id
 | 
					   * Find By Id
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  findById(id: PrimaryKey) {
 | 
					  findById(id: string | number) {
 | 
				
			||||||
    return this.collection.model.findByPk(id);
 | 
					    return this.collection.model.findByPk(id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -362,26 +362,28 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @transaction((args, transaction) => {
 | 
					  @transaction((args, transaction) => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      filterByPk: args[0],
 | 
					      filterByTk: args[0],
 | 
				
			||||||
      transaction,
 | 
					      transaction,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  async destroy(options?: PrimaryKey | PrimaryKey[] | DestroyOptions) {
 | 
					  async destroy(options?: TargetKey | TargetKey[] | DestroyOptions) {
 | 
				
			||||||
    const transaction = await this.getTransaction(options);
 | 
					    const transaction = await this.getTransaction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const modelFilterKey = this.collection.filterTargetKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    options = <DestroyOptions>options;
 | 
					    options = <DestroyOptions>options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterByPk: PrimaryKey[] | undefined =
 | 
					    const filterByTk: TargetKey[] | undefined =
 | 
				
			||||||
      options.filterByPk && !lodash.isArray(options.filterByPk)
 | 
					      options.filterByTk && !lodash.isArray(options.filterByTk)
 | 
				
			||||||
        ? [options.filterByPk]
 | 
					        ? [options.filterByTk]
 | 
				
			||||||
        : (options.filterByPk as PrimaryKey[] | undefined);
 | 
					        : (options.filterByTk as TargetKey[] | undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (filterByPk && !options.filter) {
 | 
					    if (filterByTk && !options.filter) {
 | 
				
			||||||
      return await this.model.destroy({
 | 
					      return await this.model.destroy({
 | 
				
			||||||
        ...options,
 | 
					        ...options,
 | 
				
			||||||
        where: {
 | 
					        where: {
 | 
				
			||||||
          [this.model.primaryKeyAttribute]: {
 | 
					          [modelFilterKey]: {
 | 
				
			||||||
            [Op.in]: filterByPk,
 | 
					            [Op.in]: filterByTk,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
@ -394,15 +396,15 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
          filter: options.filter,
 | 
					          filter: options.filter,
 | 
				
			||||||
          transaction,
 | 
					          transaction,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      ).map((instance) => instance[this.model.primaryKeyAttribute]);
 | 
					      ).map((instance) => instance.get(modelFilterKey) as TargetKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (filterByPk) {
 | 
					      if (filterByTk) {
 | 
				
			||||||
        pks = lodash.intersection(pks, filterByPk);
 | 
					        pks = lodash.intersection(pks, filterByTk);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return await this.destroy({
 | 
					      return await this.destroy({
 | 
				
			||||||
        context: options.context,
 | 
					        context: options.context,
 | 
				
			||||||
        filterByPk: pks,
 | 
					        filterByTk: pks,
 | 
				
			||||||
        transaction,
 | 
					        transaction,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -424,14 +426,19 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected buildQueryOptions(options: any) {
 | 
					  protected buildQueryOptions(options: any) {
 | 
				
			||||||
    const parser = new OptionsParser(this.collection.model, this.collection.context.database, options);
 | 
					    const parser = new OptionsParser(options, {
 | 
				
			||||||
 | 
					      collection: this.collection,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const params = parser.toSequelizeParams();
 | 
					    const params = parser.toSequelizeParams();
 | 
				
			||||||
    debug('sequelize query params %o', params);
 | 
					    debug('sequelize query params %o', params);
 | 
				
			||||||
    return { ...options, ...params };
 | 
					    return { ...options, ...params };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected parseFilter(filter: Filter) {
 | 
					  protected parseFilter(filter: Filter) {
 | 
				
			||||||
    const parser = new FilterParser(this.collection.model, this.collection.context.database, filter);
 | 
					    const parser = new FilterParser(filter, {
 | 
				
			||||||
 | 
					      collection: this.collection,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    return parser.toSequelizeParams();
 | 
					    return parser.toSequelizeParams();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -51,7 +51,7 @@ type UpdateValue = { [key: string]: any };
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface UpdateOptions extends TransactionAble {
 | 
					interface UpdateOptions extends TransactionAble {
 | 
				
			||||||
  filter?: any;
 | 
					  filter?: any;
 | 
				
			||||||
  filterByPk?: number | string;
 | 
					  filterByTk?: number | string;
 | 
				
			||||||
  // 字段白名单
 | 
					  // 字段白名单
 | 
				
			||||||
  whitelist?: string[];
 | 
					  whitelist?: string[];
 | 
				
			||||||
  // 字段黑名单
 | 
					  // 字段黑名单
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user