feat: acl (#153)
* feat: getRepository * getRepository return type * export action * add: acl * feat: setResourceAction * feat: action alias * chore: code struct * feat: removeResourceAction * chore: file name * ignorecase * remove ACL * feat: ACL * feat: role toJSON * using emit Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
		
							parent
							
								
									c9742c1710
								
							
						
					
					
						commit
						fd32705954
					
				
							
								
								
									
										8
									
								
								packages/acl/.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/acl/.npmignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					docs
 | 
				
			||||||
 | 
					__tests__
 | 
				
			||||||
 | 
					jest.config.js
 | 
				
			||||||
 | 
					tsconfig.json
 | 
				
			||||||
 | 
					src
 | 
				
			||||||
 | 
					.fatherrc.ts
 | 
				
			||||||
							
								
								
									
										20
									
								
								packages/acl/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/acl/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "@nocobase/acl",
 | 
				
			||||||
 | 
					  "version": "0.6.0-alpha.0",
 | 
				
			||||||
 | 
					  "description": "",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
 | 
					  "main": "./lib/index.js",
 | 
				
			||||||
 | 
					  "types": "./lib/index.d.ts",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "build": "rimraf -rf lib esm dist && npm run build:cjs && npm run build:esm",
 | 
				
			||||||
 | 
					    "build:cjs": "tsc --project tsconfig.build.json",
 | 
				
			||||||
 | 
					    "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir esm"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "repository": {
 | 
				
			||||||
 | 
					    "type": "git",
 | 
				
			||||||
 | 
					    "url": "git+https://github.com/nocobase/nocobase.git",
 | 
				
			||||||
 | 
					    "directory": "packages/acl"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										287
									
								
								packages/acl/src/__tests__/acl.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								packages/acl/src/__tests__/acl.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,287 @@
 | 
				
			|||||||
 | 
					import { ACL } from '..';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('acl', () => {
 | 
				
			||||||
 | 
					  let acl: ACL;
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    acl = new ACL();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should allow all', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableAction('edit', {
 | 
				
			||||||
 | 
					      type: 'old-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 's1',
 | 
				
			||||||
 | 
					      actions: '*',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).not.toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should deny all', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should deny when action is not available action', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).toBeNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    role.grantAction('posts:create', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should grant action when define role', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      displayName: 'create',
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:create': {},
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).not.toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  it('should grant action', function () {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      displayName: 'create',
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).toBeNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    role.grantAction('posts:create', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).not.toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should works with alias action', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('view', {
 | 
				
			||||||
 | 
					      displayName: 'view',
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					      aliases: ['get', 'list'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: ['view'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'get' })).not.toBeNull();
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'list' })).not.toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return action params when check permission', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s2', {
 | 
				
			||||||
 | 
					      displayName: 'view create update',
 | 
				
			||||||
 | 
					      actions: ['view', 'create', 'update'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableAction('view', { type: 'new-data' });
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', { type: 'new-data' });
 | 
				
			||||||
 | 
					    acl.setAvailableAction('update', { type: 'new-data' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's2',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:view': {
 | 
				
			||||||
 | 
					          filter: {
 | 
				
			||||||
 | 
					            createdById: '{{ ctx.state.currentUser.id }}',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const canResult = acl.can({ role: 'admin', resource: 'posts', action: 'view' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(canResult).toMatchObject({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      resource: 'posts',
 | 
				
			||||||
 | 
					      action: 'view',
 | 
				
			||||||
 | 
					      params: {
 | 
				
			||||||
 | 
					        filter: {
 | 
				
			||||||
 | 
					          createdById: '{{ ctx.state.currentUser.id }}',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should getActionParams', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s2', {
 | 
				
			||||||
 | 
					      displayName: 'view create update',
 | 
				
			||||||
 | 
					      actions: ['view', 'create', 'update'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableAction('view', { type: 'new-data' });
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', { type: 'new-data' });
 | 
				
			||||||
 | 
					    acl.setAvailableAction('update', { type: 'new-data' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's2',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:view': {
 | 
				
			||||||
 | 
					          filter: {
 | 
				
			||||||
 | 
					            createdById: '{{ ctx.state.currentUser.id }}',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const params = role.getActionParams('posts:view');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(params).toMatchObject({
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        createdById: '{{ ctx.state.currentUser.id }}',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should revoke action', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      displayName: 'create',
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    role.grantAction('posts:create', {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).not.toBeNull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    role.revokeAction('posts:create');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.can({ role: 'admin', resource: 'posts', action: 'create' })).toBeNull();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should call beforeGrantAction', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      type: 'old-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.beforeGrantAction('posts:create', (ctx) => {
 | 
				
			||||||
 | 
					      ctx.params = {
 | 
				
			||||||
 | 
					        filter: {
 | 
				
			||||||
 | 
					          status: 'publish',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(acl.listenerCount('posts:create.beforeGrantAction')).toEqual(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:create': {},
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const results = acl.can({ role: 'admin', resource: 'posts', action: 'create' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(results).toMatchObject({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      resource: 'posts',
 | 
				
			||||||
 | 
					      action: 'create',
 | 
				
			||||||
 | 
					      params: {
 | 
				
			||||||
 | 
					        filter: {
 | 
				
			||||||
 | 
					          status: 'publish',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should to JSON', () => {
 | 
				
			||||||
 | 
					    acl.setAvailableAction('create', {
 | 
				
			||||||
 | 
					      displayName: 'create',
 | 
				
			||||||
 | 
					      type: 'new-data',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    acl.setAvailableStrategy('s1', {
 | 
				
			||||||
 | 
					      displayName: 'test',
 | 
				
			||||||
 | 
					      actions: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const role = acl.define({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:create': {
 | 
				
			||||||
 | 
					          filter: { a: 'b' },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const roleJSON = role.toJSON();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(roleJSON).toMatchObject({
 | 
				
			||||||
 | 
					      role: 'admin',
 | 
				
			||||||
 | 
					      strategy: 's1',
 | 
				
			||||||
 | 
					      actions: {
 | 
				
			||||||
 | 
					        'posts:create': {},
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										10
									
								
								packages/acl/src/acl-available-action.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/acl/src/acl-available-action.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					export interface AvailableActionOptions {
 | 
				
			||||||
 | 
					  aliases?: string[] | string;
 | 
				
			||||||
 | 
					  type: 'new-data' | 'old-data';
 | 
				
			||||||
 | 
					  displayName?: string;
 | 
				
			||||||
 | 
					  resource?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AclAvailableAction {
 | 
				
			||||||
 | 
					  constructor(private name: string, private options: AvailableActionOptions) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								packages/acl/src/acl-available-strategy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								packages/acl/src/acl-available-strategy.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import lodash from 'lodash';
 | 
				
			||||||
 | 
					type StrategyValue = false | '*' | string | string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AvailableStrategyOptions {
 | 
				
			||||||
 | 
					  displayName?: string;
 | 
				
			||||||
 | 
					  actions: false | string | string[];
 | 
				
			||||||
 | 
					  resource?: '*';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function strategyValueMatched(strategy: StrategyValue, value: string) {
 | 
				
			||||||
 | 
					  if (strategy === '*') {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (lodash.isString(strategy) && strategy === value) {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (lodash.isArray(strategy) && strategy.includes(value)) {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ACLAvailableStrategy {
 | 
				
			||||||
 | 
					  options: AvailableStrategyOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(options: AvailableStrategyOptions) {
 | 
				
			||||||
 | 
					    this.options = options;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  matchAction(actionName: string) {
 | 
				
			||||||
 | 
					    return strategyValueMatched(this.options.actions, actionName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  allow(resourceName: string, actionName: string) {
 | 
				
			||||||
 | 
					    return this.matchAction(actionName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								packages/acl/src/acl-resource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								packages/acl/src/acl-resource.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					import { ACLRole, RoleActionParams } from './acl-role';
 | 
				
			||||||
 | 
					import { ACL, ListenerContext } from './acl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ResourceActions = { [key: string]: RoleActionParams };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AclResourceOptions {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  role: ACLRole;
 | 
				
			||||||
 | 
					  actions?: ResourceActions;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ACLResource {
 | 
				
			||||||
 | 
					  actions = new Map<string, RoleActionParams>();
 | 
				
			||||||
 | 
					  acl: ACL;
 | 
				
			||||||
 | 
					  role: ACLRole;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(options: AclResourceOptions) {
 | 
				
			||||||
 | 
					    this.acl = options.role.acl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.role = options.role;
 | 
				
			||||||
 | 
					    this.name = options.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const actionsOption: ResourceActions = options.actions || {};
 | 
				
			||||||
 | 
					    for (const actionName of Object.keys(actionsOption)) {
 | 
				
			||||||
 | 
					      this.actions.set(actionName, actionsOption[actionName]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getActions() {
 | 
				
			||||||
 | 
					    return Array.from(this.actions.keys()).reduce((carry, key) => {
 | 
				
			||||||
 | 
					      carry[key] = this.actions.get(key);
 | 
				
			||||||
 | 
					      return carry;
 | 
				
			||||||
 | 
					    }, {});
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getAction(name: string) {
 | 
				
			||||||
 | 
					    return this.actions.get(name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setAction(name: string, params: RoleActionParams) {
 | 
				
			||||||
 | 
					    const context: ListenerContext = {
 | 
				
			||||||
 | 
					      role: this.role,
 | 
				
			||||||
 | 
					      acl: this.role.acl,
 | 
				
			||||||
 | 
					      params: params || {},
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.acl.emit(`${this.name}:${name}.beforeGrantAction`, context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.actions.set(name, context.params);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setActions(actions: { [key: string]: RoleActionParams }) {
 | 
				
			||||||
 | 
					    for (const actionName of Object.keys(actions)) {
 | 
				
			||||||
 | 
					      this.setAction(actionName, actions[actionName]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  removeAction(name: string) {
 | 
				
			||||||
 | 
					    this.actions.delete(name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										116
									
								
								packages/acl/src/acl-role.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								packages/acl/src/acl-role.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import { ACLResource } from './acl-resource';
 | 
				
			||||||
 | 
					import { AvailableStrategyOptions } from './acl-available-strategy';
 | 
				
			||||||
 | 
					import { ACL, DefineOptions } from './acl';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RoleActionParams {
 | 
				
			||||||
 | 
					  fields?: string[];
 | 
				
			||||||
 | 
					  filter?: any;
 | 
				
			||||||
 | 
					  own?: boolean;
 | 
				
			||||||
 | 
					  whitelist?: string[];
 | 
				
			||||||
 | 
					  blacklist?: string[];
 | 
				
			||||||
 | 
					  [key: string]: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ResourceActionsOptions {
 | 
				
			||||||
 | 
					  [actionName: string]: RoleActionParams;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ACLRole {
 | 
				
			||||||
 | 
					  strategy: string | AvailableStrategyOptions;
 | 
				
			||||||
 | 
					  resources = new Map<string, ACLResource>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(public acl: ACL, public name: string) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getResource(name: string): ACLResource | undefined {
 | 
				
			||||||
 | 
					    return this.resources.get(name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setResource(name: string, resource: ACLResource) {
 | 
				
			||||||
 | 
					    this.resources.set(name, resource);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public setStrategy(value: string | AvailableStrategyOptions) {
 | 
				
			||||||
 | 
					    this.strategy = value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public grantResource(resourceName: string, options: ResourceActionsOptions) {
 | 
				
			||||||
 | 
					    const resource = new ACLResource({
 | 
				
			||||||
 | 
					      role: this,
 | 
				
			||||||
 | 
					      name: resourceName,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [actionName, actionParams] of Object.entries(options)) {
 | 
				
			||||||
 | 
					      resource.setAction(actionName, actionParams);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.resources.set(resourceName, resource);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public getResourceActionsParams(resourceName: string) {
 | 
				
			||||||
 | 
					    const resource = this.getResource(resourceName);
 | 
				
			||||||
 | 
					    return resource.getActions();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public revokeResource(resourceName) {
 | 
				
			||||||
 | 
					    this.resources.delete(resourceName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public grantAction(path: string, options?: RoleActionParams) {
 | 
				
			||||||
 | 
					    let { resource, resourceName, actionName } = this.getResourceActionFromPath(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!resource) {
 | 
				
			||||||
 | 
					      resource = new ACLResource({
 | 
				
			||||||
 | 
					        role: this,
 | 
				
			||||||
 | 
					        name: resourceName,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.resources.set(resourceName, resource);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resource.setAction(actionName, options);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public getActionParams(path: string): RoleActionParams {
 | 
				
			||||||
 | 
					    const { action } = this.getResourceActionFromPath(path);
 | 
				
			||||||
 | 
					    return action;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public revokeAction(path: string) {
 | 
				
			||||||
 | 
					    const { resource, actionName } = this.getResourceActionFromPath(path);
 | 
				
			||||||
 | 
					    resource.removeAction(actionName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public toJSON(): DefineOptions {
 | 
				
			||||||
 | 
					    const actions = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const resourceName of this.resources.keys()) {
 | 
				
			||||||
 | 
					      const resourceActions = this.getResourceActionsParams(resourceName);
 | 
				
			||||||
 | 
					      for (const actionName of Object.keys(resourceActions)) {
 | 
				
			||||||
 | 
					        actions[`${resourceName}:${actionName}`] = resourceActions[actionName];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      role: this.name,
 | 
				
			||||||
 | 
					      strategy: this.strategy,
 | 
				
			||||||
 | 
					      actions,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected getResourceActionFromPath(path: string) {
 | 
				
			||||||
 | 
					    const [resourceName, actionName] = path.split(':');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resource = this.resources.get(resourceName);
 | 
				
			||||||
 | 
					    let action = null;
 | 
				
			||||||
 | 
					    if (resource) {
 | 
				
			||||||
 | 
					      action = resource.getAction(actionName);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      resourceName,
 | 
				
			||||||
 | 
					      actionName,
 | 
				
			||||||
 | 
					      resource,
 | 
				
			||||||
 | 
					      action,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								packages/acl/src/acl.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								packages/acl/src/acl.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					import lodash from 'lodash';
 | 
				
			||||||
 | 
					import { ACLAvailableStrategy, AvailableStrategyOptions } from './acl-available-strategy';
 | 
				
			||||||
 | 
					import { ACLRole, RoleActionParams } from './acl-role';
 | 
				
			||||||
 | 
					import { AclAvailableAction, AvailableActionOptions } from './acl-available-action';
 | 
				
			||||||
 | 
					import EventEmitter from 'events';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface StrategyOptions {
 | 
				
			||||||
 | 
					  role: string;
 | 
				
			||||||
 | 
					  strategy: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CanResult {
 | 
				
			||||||
 | 
					  role: string;
 | 
				
			||||||
 | 
					  resource: string;
 | 
				
			||||||
 | 
					  action: string;
 | 
				
			||||||
 | 
					  params?: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface DefineOptions {
 | 
				
			||||||
 | 
					  role: string;
 | 
				
			||||||
 | 
					  strategy?: string | AvailableStrategyOptions;
 | 
				
			||||||
 | 
					  actions?: {
 | 
				
			||||||
 | 
					    [key: string]: RoleActionParams;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  routes?: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ListenerContext {
 | 
				
			||||||
 | 
					  acl: ACL;
 | 
				
			||||||
 | 
					  role: ACLRole;
 | 
				
			||||||
 | 
					  params: RoleActionParams;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Listener = (ctx: ListenerContext) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ACL extends EventEmitter {
 | 
				
			||||||
 | 
					  protected availableActions = new Map<string, AclAvailableAction>();
 | 
				
			||||||
 | 
					  protected availableStrategy = new Map<string, ACLAvailableStrategy>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  roles = new Map<string, ACLRole>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  actionAlias = new Map<string, string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  define(options: DefineOptions): ACLRole {
 | 
				
			||||||
 | 
					    const roleName = options.role;
 | 
				
			||||||
 | 
					    const role = new ACLRole(this, roleName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (options.strategy) {
 | 
				
			||||||
 | 
					      role.strategy = options.strategy;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const actions = options.actions || {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [actionName, actionParams] of Object.entries(actions)) {
 | 
				
			||||||
 | 
					      role.grantAction(actionName, actionParams);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.roles.set(roleName, role);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return role;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRole(name: string): ACLRole {
 | 
				
			||||||
 | 
					    return this.roles.get(name);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setAvailableAction(name: string, options: AvailableActionOptions) {
 | 
				
			||||||
 | 
					    this.availableActions.set(name, new AclAvailableAction(name, options));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (options.aliases) {
 | 
				
			||||||
 | 
					      const aliases = lodash.isArray(options.aliases) ? options.aliases : [options.aliases];
 | 
				
			||||||
 | 
					      for (const alias of aliases) {
 | 
				
			||||||
 | 
					        this.actionAlias.set(alias, name);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setAvailableStrategy(name: string, options: AvailableStrategyOptions) {
 | 
				
			||||||
 | 
					    this.availableStrategy.set(name, new ACLAvailableStrategy(options));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeGrantAction(path: string, listener?: Listener) {
 | 
				
			||||||
 | 
					    this.addListener(`${path}.beforeGrantAction`, listener);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  can({ role, resource, action }: { role: string; resource: string; action: string }): CanResult | null {
 | 
				
			||||||
 | 
					    action = this.resolveActionAlias(action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!this.isAvailableAction(action)) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const aclRole = this.roles.get(role);
 | 
				
			||||||
 | 
					    const aclResource = aclRole.getResource(resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (aclResource) {
 | 
				
			||||||
 | 
					      const aclActionConfig = aclResource.actions.get(this.resolveActionAlias(action));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (aclActionConfig) {
 | 
				
			||||||
 | 
					        // handle single action config
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          role,
 | 
				
			||||||
 | 
					          resource,
 | 
				
			||||||
 | 
					          action,
 | 
				
			||||||
 | 
					          params: aclActionConfig,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const roleStrategy = lodash.isString(aclRole.strategy)
 | 
				
			||||||
 | 
					      ? this.availableStrategy.get(aclRole.strategy)
 | 
				
			||||||
 | 
					      : new ACLAvailableStrategy(aclRole.strategy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!roleStrategy) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (roleStrategy.allow(resource, action)) {
 | 
				
			||||||
 | 
					      return { role, resource, action };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected isAvailableAction(actionName: string) {
 | 
				
			||||||
 | 
					    return this.availableActions.has(actionName);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected resolveActionAlias(action: string) {
 | 
				
			||||||
 | 
					    return this.actionAlias.get(action) ? this.actionAlias.get(action) : action;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								packages/acl/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/acl/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					export * from './acl-resource';
 | 
				
			||||||
 | 
					export * from './acl-role';
 | 
				
			||||||
 | 
					export * from './acl-available-strategy';
 | 
				
			||||||
 | 
					export * from './acl-available-action';
 | 
				
			||||||
 | 
					export * from './acl';
 | 
				
			||||||
							
								
								
									
										9
									
								
								packages/acl/tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/acl/tsconfig.build.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../../tsconfig.build.json",
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "outDir": "./lib",
 | 
				
			||||||
 | 
					    "declaration": true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "include": ["./src/**/*.ts", "./src/**/*.tsx"],
 | 
				
			||||||
 | 
					  "exclude": ["./src/__tests__/*", "./esm/*", "./lib/*"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								packages/acl/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/acl/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../../tsconfig.json",
 | 
				
			||||||
 | 
					  "include": ["./src/**/*.ts", "./src/**/*.tsx"],
 | 
				
			||||||
 | 
					  "exclude": ["./esm/*", "./lib/*"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -19,4 +19,4 @@ export function registerActions(api: any) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {};
 | 
					export default actions;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,23 @@ import path from 'path';
 | 
				
			|||||||
import { Model } from '..';
 | 
					import { Model } from '..';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('database', () => {
 | 
					describe('database', () => {
 | 
				
			||||||
 | 
					  test('get repository', async () => {
 | 
				
			||||||
 | 
					    const db = mockDatabase();
 | 
				
			||||||
 | 
					    db.collection({
 | 
				
			||||||
 | 
					      name: 'tests',
 | 
				
			||||||
 | 
					      fields: [{ type: 'hasMany', name: 'relations' }],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.collection({
 | 
				
			||||||
 | 
					      name: 'relations',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(db.getRepository('tests')).toEqual(db.getCollection('tests').repository);
 | 
				
			||||||
 | 
					    expect(db.getRepository('tests.relations', '1')).toEqual(
 | 
				
			||||||
 | 
					      db.getCollection('tests').repository.relation('relations').of('1'),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('import', async () => {
 | 
					  test('import', async () => {
 | 
				
			||||||
    const db = mockDatabase();
 | 
					    const db = mockDatabase();
 | 
				
			||||||
    await db.import({
 | 
					    await db.import({
 | 
				
			||||||
@ -191,5 +208,4 @@ describe('database', () => {
 | 
				
			|||||||
    test.customMethod();
 | 
					    test.customMethod();
 | 
				
			||||||
    expect(test.get('abc')).toBe('abc');
 | 
					    expect(test.get('abc')).toBe('abc');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,8 @@ import { ModelHook } from './model-hook';
 | 
				
			|||||||
import { ImporterReader, ImportFileExtension } from './collection-importer';
 | 
					import { ImporterReader, ImportFileExtension } from './collection-importer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import extendOperators from './operators';
 | 
					import extendOperators from './operators';
 | 
				
			||||||
 | 
					import { Repository } from './repository';
 | 
				
			||||||
 | 
					import { RelationRepository } from './relation-repository/relation-repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MergeOptions extends merge.Options {}
 | 
					export interface MergeOptions extends merge.Options {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -126,6 +128,18 @@ export class Database extends EventEmitter implements AsyncEmitter {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRepository(name: string): Repository;
 | 
				
			||||||
 | 
					  getRepository<R extends RelationRepository>(name: string, relationId: string | number): R;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRepository<R extends RelationRepository>(name: string, relationId?: string | number): Repository | R {
 | 
				
			||||||
 | 
					    if (relationId) {
 | 
				
			||||||
 | 
					      const [collection, relation] = name.split('.');
 | 
				
			||||||
 | 
					      return this.getRepository(collection).relation(relation).of(relationId) as R;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.getCollection(name).repository;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  addPendingField(field: RelationField) {
 | 
					  addPendingField(field: RelationField) {
 | 
				
			||||||
    const associating = this.pendingFields;
 | 
					    const associating = this.pendingFields;
 | 
				
			||||||
    const items = this.pendingFields.get(field.target) || [];
 | 
					    const items = this.pendingFields.get(field.target) || [];
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user