├── test ├── fixtures │ ├── di │ │ ├── config │ │ │ ├── config.default.ts │ │ │ └── plugin.ts │ │ ├── package.json │ │ └── app │ │ │ ├── service │ │ │ ├── app.ts │ │ │ ├── test.ts │ │ │ └── test2.ts │ │ │ ├── router.ts │ │ │ └── controller │ │ │ └── home.ts │ ├── ctx-proxy │ │ ├── package.json │ │ ├── config │ │ │ ├── plugin.ts │ │ │ └── config.default.ts │ │ └── app │ │ │ ├── router.ts │ │ │ ├── service │ │ │ └── app.ts │ │ │ └── controller │ │ │ └── home.ts │ └── di-auto-register │ │ ├── package.json │ │ ├── config │ │ ├── plugin.ts │ │ └── config.default.ts │ │ └── app │ │ ├── router.ts │ │ ├── service │ │ └── test.ts │ │ └── controller │ │ └── home.ts ├── di-autoreg.test.ts ├── tsconfig.json ├── ctx-proxy.test.ts ├── inject.test.ts ├── getInstance.test.ts ├── hook.test.ts ├── di.test.ts └── aspect.test.ts ├── config └── config.default.ts ├── .travis.yml ├── scripts └── published.ts ├── .gitignore ├── appveyor.yml ├── typings ├── app │ └── extend │ │ ├── context.d.ts │ │ ├── context.d.d.ts │ │ ├── application.d.ts │ │ └── application.d.d.ts └── config │ └── index.d.ts ├── lib ├── typeLoader.ts ├── index.ts ├── appctx.ts ├── decorators.ts ├── getInstance.ts └── aspect.ts ├── .autod.conf.js ├── .gitlab-ci.yml ├── History.md ├── app └── extend │ ├── context.ts │ └── application.ts ├── tsconfig.json ├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── tslint.json ├── package.json └── README.md /test/fixtures/di/config/config.default.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.keys = '123456'; 4 | -------------------------------------------------------------------------------- /test/fixtures/di/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aop-example", 3 | "egg": { 4 | "typescript": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aop-example", 3 | "egg": { 4 | "typescript": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aop-example", 3 | "egg": { 4 | "typescript": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/di/config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg'; 2 | 3 | const plugin: EggPlugin = { 4 | }; 5 | 6 | export default plugin; 7 | -------------------------------------------------------------------------------- /config/config.default.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | aop: { 3 | useCtxProxyForAppComponent: false, 4 | autoRegisterToCtx: false, 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg'; 2 | 3 | const plugin: EggPlugin = { 4 | }; 5 | 6 | export default plugin; 7 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/config/plugin.ts: -------------------------------------------------------------------------------- 1 | import { EggPlugin } from 'egg'; 2 | 3 | const plugin: EggPlugin = { 4 | }; 5 | 6 | export default plugin; 7 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/config/config.default.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | keys: '123456', 5 | aop: { 6 | useCtxProxyForAppComponent: true, 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/config/config.default.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | keys: '123456', 5 | aop: { 6 | autoRegisterToCtx: true, 7 | useCtxProxyForAppComponent: true, 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | 6 | router.get('/', (controller as any).home.index); 7 | }; 8 | -------------------------------------------------------------------------------- /scripts/published.ts: -------------------------------------------------------------------------------- 1 | const shell = require('child_process').execSync; 2 | 3 | const package = require('../package.json'); 4 | 5 | shell(`git tag ${package.version}`); 6 | shell(`git push`); 7 | shell(`git push origin ${package.version}`); 8 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | 6 | router.get('/appcount', (controller as any).home.appCount); 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/di/app/service/app.ts: -------------------------------------------------------------------------------- 1 | import { application } from '../../../../../lib'; 2 | 3 | @application() 4 | export class AppLevelService { 5 | 6 | private count = 0; 7 | 8 | public async get() { 9 | return this.count++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/app/service/app.ts: -------------------------------------------------------------------------------- 1 | import { application } from '../../../../../lib'; 2 | 3 | @application() 4 | export class AppLevelService { 5 | 6 | private count = 0; 7 | 8 | public async get() { 9 | return this.count++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/app/service/test.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | 3 | export class TestService extends Service { 4 | 5 | /** 6 | * sayHi to you 7 | * @param name - your name 8 | */ 9 | public async sayHi(name: string) { 10 | return `hi, ${name}`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | run/ 7 | .DS_Store 8 | *.swp 9 | package-lock.json 10 | /app/**/*.js 11 | /lib/**/*.js 12 | /test/**/*.js 13 | /config/**/*.js 14 | /scripts/**/*.js 15 | *.d.ts 16 | *.js.map 17 | .vscode 18 | !typings/**/*.d.ts 19 | .nyc_output 20 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /test/fixtures/di/app/service/test.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import { context } from '../../../../../lib'; 3 | 4 | @context() 5 | export class TestService extends Service { 6 | 7 | /** 8 | * sayHi to you 9 | * @param name - your name 10 | */ 11 | public async sayHi(name: string) { 12 | return `hi, ${name}`; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typings/app/extend/context.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import ExtendContext from '../../../app/extend/context'; 6 | declare module 'egg' { 7 | type ExtendContextType = typeof ExtendContext; 8 | interface Context extends ExtendContextType { } 9 | } -------------------------------------------------------------------------------- /typings/app/extend/context.d.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import ExtendDContext from '../../../app/extend/context.d'; 6 | declare module 'egg' { 7 | type ExtendDContextType = typeof ExtendDContext; 8 | interface Context extends ExtendDContextType { } 9 | } -------------------------------------------------------------------------------- /typings/app/extend/application.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import ExtendApplication from '../../../app/extend/application'; 6 | declare module 'egg' { 7 | type ExtendApplicationType = typeof ExtendApplication; 8 | interface Application extends ExtendApplicationType { } 9 | } -------------------------------------------------------------------------------- /typings/app/extend/application.d.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import ExtendDApplication from '../../../app/extend/application.d'; 6 | declare module 'egg' { 7 | type ExtendDApplicationType = typeof ExtendDApplication; 8 | interface Application extends ExtendDApplicationType { } 9 | } -------------------------------------------------------------------------------- /lib/typeLoader.ts: -------------------------------------------------------------------------------- 1 | import { IocContext } from 'power-di'; 2 | import { contextTypeSymbol, InstanceSource } from './getInstance'; 3 | 4 | export const typeLoader = new IocContext; 5 | 6 | export function register(clsType: any, keyType: any, from: InstanceSource) { 7 | Object.defineProperty(clsType, contextTypeSymbol, { 8 | value: from 9 | }); 10 | typeLoader.register(clsType, keyType, { autoNew: false }); 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/di-auto-register/app/controller/home.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { lazyInject } from '../../../../../lib'; 3 | import { TestService } from '../service/test'; 4 | 5 | export default class HomeController extends Controller { 6 | @lazyInject() 7 | private testService: TestService; 8 | 9 | public async index() { 10 | this.ctx.body = await this.testService.sayHi('egg'); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | test: [ 7 | 'test', 8 | 'benchmark', 9 | ], 10 | dep: [ 11 | 'egg' 12 | ], 13 | devdep: [ 14 | 'egg-ci', 15 | 'egg-bin', 16 | 'autod', 17 | 'tslint', 18 | 'supertest', 19 | 'webstorm-disable-index', 20 | ], 21 | exclude: [ 22 | './test/fixtures', 23 | './dist', 24 | ], 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # 不要修改该文件, 会自动生成, 详见 http://gitlab.alibaba-inc.com/node/ci 2 | before_script: 3 | - export PATH=$PWD/node_modules/.bin:$PATH 4 | - echo $PATH 5 | - time enclose install tnpm 6 | - tnpm -v 7 | node-8: 8 | image: reg.docker.alibaba-inc.com/dockerlab/node-ci:2.3.0 9 | script: 10 | - time tnpm i --install-node=8 --no-cache 11 | - which node && node -p 'process.versions' 12 | - time tnpm run ci 13 | tags: 14 | - swarm 15 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.5.3 / 2018-09-21 3 | ================== 4 | 5 | * export aspect funcWrapper. 6 | 7 | 0.5.2 / 2018-08-09 8 | ================== 9 | 10 | * feat: add context to Promise return value for aspect. 11 | * fix: maybe trigger getter when lazyInject read default value. 12 | 13 | 0.5.1 / 2018-06-26 14 | ================== 15 | 16 | * feat: add remove/clear instance hook method. 17 | * refactor: optimization type definition of getInstance. 18 | -------------------------------------------------------------------------------- /typings/config/index.d.ts: -------------------------------------------------------------------------------- 1 | // This file was auto created by egg-ts-helper 2 | // Do not modify this file!!!!!!!!! 3 | 4 | import 'egg'; // Make sure ts to import egg declaration at first 5 | import { EggAppConfig } from 'egg'; 6 | import ExportConfigDefault from '../../config/config.default'; 7 | type ConfigDefault = typeof ExportConfigDefault; 8 | declare module 'egg' { 9 | type NewEggAppConfig = ConfigDefault; 10 | interface EggAppConfig extends NewEggAppConfig { } 11 | } -------------------------------------------------------------------------------- /app/extend/context.ts: -------------------------------------------------------------------------------- 1 | import { IocContext } from 'power-di'; 2 | import { getInstance } from '../../lib'; 3 | 4 | const IOC = Symbol('Context#PowerDI'); 5 | 6 | export default { 7 | get iocContext() { 8 | if (!this[IOC]) { 9 | this[IOC] = new IocContext(); 10 | } 11 | return this[IOC]; 12 | }, 13 | get getComponent() { 14 | return function (clsType: any) { 15 | return getInstance(clsType, this.app, this); 16 | }; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/di/app/service/test2.ts: -------------------------------------------------------------------------------- 1 | import { Service } from 'egg'; 2 | import { context, lazyInject } from '../../../../../lib'; 3 | import { TestService } from './test'; 4 | 5 | @context() 6 | export class Test2Service extends Service { 7 | 8 | @lazyInject() 9 | private testService: TestService; 10 | 11 | /** 12 | * sayHi to you 13 | * @param name - your name 14 | */ 15 | public async sayHi(name: string) { 16 | return this.testService.sayHi(name); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/extend/application.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { IocContext } from 'power-di'; 3 | import { getInstance } from '../../lib'; 4 | 5 | const IOC = Symbol('Application#PowerDI'); 6 | 7 | export default { 8 | get iocContext() { 9 | if (!this[IOC]) { 10 | this[IOC] = new IocContext(); 11 | } 12 | return this[IOC]; 13 | }, 14 | get getComponent() { 15 | return function (clsType: any) { 16 | return getInstance(clsType, this, undefined); 17 | }; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/fixtures/ctx-proxy/app/controller/home.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { lazyInject } from '../../../../../lib'; 3 | import { AppLevelService } from '../service/app'; 4 | 5 | export default class HomeController extends Controller { 6 | 7 | @lazyInject() 8 | private appLevelService: AppLevelService; 9 | 10 | public async appCount() { 11 | this.ctx.body = { 12 | count: await this.appLevelService.get(), 13 | inCtx: !!this.ctx.getComponent(AppLevelService), 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/di-autoreg.test.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'supertest'; 2 | import mm from 'egg-mock'; 3 | 4 | describe('di auto register', () => { 5 | let app: any; 6 | before(() => { 7 | app = mm.app({ 8 | baseDir: 'di-auto-register', 9 | }); 10 | return app.ready(); 11 | }); 12 | 13 | after(() => app.close()); 14 | 15 | afterEach(mm.restore); 16 | 17 | it('lazyInject', () => { 18 | return request(app.callback()) 19 | .get('/') 20 | .expect('hi, egg') 21 | .expect(200); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "jsx": "react", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "preserveConstEnums": true, 11 | "declaration": true, 12 | "sourceMap": false, 13 | "pretty": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "importHelpers": true 17 | }, 18 | "compileOnSave": true 19 | } 20 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export { getApp, getCtx } from './appctx'; 3 | export * from './getInstance'; 4 | export { typeLoader } from './typeLoader'; 5 | export * from './aspect'; 6 | 7 | import 'egg'; 8 | import { GetReturnType } from 'power-di'; 9 | declare module 'egg' { 10 | export interface Context { 11 | getComponent(clsType: KeyType): GetReturnType; 12 | } 13 | export interface Application { 14 | getComponent(clsType: KeyType): GetReturnType; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/di/app/router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'egg'; 2 | 3 | export default (app: Application) => { 4 | const { controller, router } = app; 5 | 6 | router.get('/', (controller as any).home.index); 7 | router.get('/inject', (controller as any).home.inject); 8 | router.get('/notype', (controller as any).home.notype); 9 | router.get('/getcomponent', (controller as any).home.getComponent); 10 | router.get('/appcount', (controller as any).home.appCount); 11 | router.get('/appgetcomponent', (controller as any).home.appGetComponent); 12 | router.get('/mutli', (controller as any).home.mutli); 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "jsx": "react", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "preserveConstEnums": true, 11 | "declaration": true, 12 | "sourceMap": false, 13 | "pretty": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "importHelpers": true 17 | }, 18 | "include": [ 19 | "app", 20 | "lib", 21 | "config", 22 | "scripts" 23 | ], 24 | "compileOnSave": true 25 | } 26 | -------------------------------------------------------------------------------- /lib/appctx.ts: -------------------------------------------------------------------------------- 1 | export const ctxSymbol = Symbol('ctx'); 2 | export function setCtx(target: any, ctx: any) { 3 | Object.defineProperty(target, ctxSymbol, { 4 | enumerable: false, 5 | writable: false, 6 | value: ctx 7 | }); 8 | } 9 | export function getCtx(target: any) { 10 | return target[ctxSymbol] || target.ctx; 11 | } 12 | 13 | export const appSymbol = Symbol('app'); 14 | export function setApp(target: any, app: any) { 15 | Object.defineProperty(target, appSymbol, { 16 | enumerable: false, 17 | writable: false, 18 | value: app 19 | }); 20 | } 21 | export function getApp(target: any) { 22 | return target[appSymbol] || target.app; 23 | } 24 | -------------------------------------------------------------------------------- /test/ctx-proxy.test.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'supertest'; 2 | import mm from 'egg-mock'; 3 | 4 | describe('di normal', () => { 5 | let app: any; 6 | before(() => { 7 | app = mm.app({ 8 | baseDir: 'ctx-proxy', 9 | }); 10 | return app.ready(); 11 | }); 12 | 13 | after(() => app.close()); 14 | 15 | afterEach(mm.restore); 16 | 17 | it('get component by method', async () => { 18 | await request(app.callback()) 19 | .get('/appcount') 20 | .expect('{"count":0,"inCtx":true}') 21 | .expect(200); 22 | 23 | return request(app.callback()) 24 | .get('/appcount') 25 | .expect('{"count":1,"inCtx":true}') 26 | .expect(200); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/inject.test.ts: -------------------------------------------------------------------------------- 1 | import mm from 'egg-mock'; 2 | import assert = require('power-assert'); 3 | import { inject, context } from '../lib'; 4 | import { setApp, setCtx } from '../lib/appctx'; 5 | 6 | describe('inject', () => { 7 | let app: any; 8 | let ctx: any; 9 | 10 | before(() => { 11 | app = mm.app({ 12 | baseDir: 'di', 13 | }); 14 | return app.ready(); 15 | }); 16 | 17 | beforeEach(() => { 18 | ctx = app.createAnonymousContext(); 19 | }); 20 | 21 | afterEach(() => { 22 | mm.restore(); 23 | }); 24 | 25 | it('normal', () => { 26 | @context() 27 | class BService { } 28 | 29 | class AService { 30 | @inject() 31 | bService: BService; 32 | } 33 | 34 | const aService = new AService; 35 | setApp(aService, app); 36 | setCtx(aService, ctx); 37 | assert.ok(aService.bService instanceof BService); 38 | 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ##### Checklist 12 | 13 | 14 | - [ ] `npm test` passes 15 | - [ ] tests and/or benchmarks are included 16 | - [ ] documentation is changed or added 17 | - [ ] commit message follows commit guidelines 18 | 19 | ##### Affected core subsystem(s) 20 | 21 | 22 | 23 | ##### Description of change 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SuperEVO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/getInstance.test.ts: -------------------------------------------------------------------------------- 1 | import mm from 'egg-mock'; 2 | import assert = require('power-assert'); 3 | import { getInstance, context, application } from '../lib'; 4 | 5 | describe('getInstance', () => { 6 | let app: any; 7 | let ctx: any; 8 | 9 | before(() => { 10 | app = mm.app({ 11 | baseDir: 'di', 12 | }); 13 | return app.ready(); 14 | }); 15 | 16 | beforeEach(() => { 17 | ctx = app.createAnonymousContext(); 18 | }); 19 | 20 | afterEach(() => { 21 | mm.restore(); 22 | }); 23 | 24 | it('normal', () => { 25 | @context() 26 | class AService { } 27 | 28 | const aService = getInstance(AService, app, ctx); 29 | assert.ok(aService instanceof AService); 30 | 31 | @application() 32 | class BService { } 33 | 34 | const bService = getInstance(BService, app); 35 | assert.ok(bService instanceof BService); 36 | }); 37 | 38 | it('fail, no register', () => { 39 | class AService { } 40 | 41 | assert.throws(() => { 42 | getInstance(AService, app, ctx); 43 | }); 44 | }); 45 | 46 | it('fail, no ctx', () => { 47 | @context() 48 | class AService { } 49 | 50 | assert.throws(() => { 51 | getInstance(AService, app, undefined); 52 | }); 53 | }); 54 | 55 | it('fail, no app', () => { 56 | @application() 57 | class AService { } 58 | 59 | assert.throws(() => { 60 | getInstance(AService, undefined, undefined); 61 | }); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "member-ordering": [ 13 | true, 14 | "public-before-private", 15 | "static-before-instance", 16 | "variables-before-functions" 17 | ], 18 | "no-conditional-assignment": true, 19 | "no-duplicate-variable": true, 20 | "no-eval": true, 21 | "no-internal-module": true, 22 | "no-trailing-whitespace": true, 23 | "no-unused-variable": false, 24 | "no-var-keyword": true, 25 | "one-line": [ 26 | true, 27 | "check-open-brace", 28 | "check-whitespace" 29 | ], 30 | "quotemark": [ 31 | true, 32 | "single", 33 | "jsx-double" 34 | ], 35 | "semicolon": [ 36 | true, 37 | "always" 38 | ], 39 | "typedef-whitespace": [ 40 | true, 41 | { 42 | "call-signature": "nospace", 43 | "index-signature": "nospace", 44 | "parameter": "nospace", 45 | "property-declaration": "nospace", 46 | "variable-declaration": "nospace" 47 | } 48 | ], 49 | "variable-name": [ 50 | true, 51 | "ban-keywords" 52 | ], 53 | "whitespace": [ 54 | true, 55 | "check-branch", 56 | "check-decl", 57 | "check-operator", 58 | "check-separator", 59 | "check-type" 60 | ] 61 | } 62 | } -------------------------------------------------------------------------------- /test/fixtures/di/app/controller/home.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from 'egg'; 2 | import { lazyInject, inject } from '../../../../../lib'; 3 | import { TestService } from '../service/test'; 4 | import { AppLevelService } from '../service/app'; 5 | import { Test2Service } from '../service/test2'; 6 | 7 | export default class HomeController extends Controller { 8 | @lazyInject() 9 | private testService: TestService; 10 | 11 | @lazyInject(TestService) 12 | private testService2: any; // without typescript metadata gen. 13 | 14 | @inject() 15 | private testService3: TestService; 16 | 17 | @lazyInject() 18 | private appLevelService: AppLevelService; 19 | 20 | @lazyInject() 21 | private testService4: Test2Service; 22 | 23 | public async index() { 24 | this.ctx.body = await this.testService.sayHi('egg'); 25 | } 26 | 27 | public async inject() { 28 | this.ctx.body = await this.testService3.sayHi('egg'); 29 | } 30 | 31 | public async notype() { 32 | this.ctx.body = await this.testService2.sayHi('egg'); 33 | } 34 | 35 | public async getComponent() { 36 | this.ctx.body = await this.ctx.getComponent(TestService).sayHi('service'); 37 | } 38 | 39 | public async appCount() { 40 | this.ctx.body = await this.appLevelService.get(); 41 | } 42 | 43 | public async appGetComponent() { 44 | this.ctx.body = await this.app.getComponent(AppLevelService).get(); 45 | } 46 | 47 | public async mutli() { 48 | this.ctx.body = await this.testService4.sayHi('egg'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/hook.test.ts: -------------------------------------------------------------------------------- 1 | import { setCreateInstanceHook, removeCreateInstanceHook, clearCreateInstanceHook } from '../lib'; 2 | 3 | import * as request from 'supertest'; 4 | import mm from 'egg-mock'; 5 | 6 | describe('setCreateInstanceHook', () => { 7 | let app: any; 8 | before(() => { 9 | app = mm.app({ 10 | baseDir: 'di', 11 | }); 12 | return app.ready(); 13 | }); 14 | 15 | after(() => app.close()); 16 | 17 | afterEach(() => { 18 | mm.restore(); 19 | clearCreateInstanceHook(); 20 | }); 21 | 22 | it('normal', () => { 23 | setCreateInstanceHook((_inst, _app, _ctx) => { 24 | return { 25 | sayHi() { 26 | return 'setCreateInstanceHook'; 27 | } 28 | }; 29 | }); 30 | 31 | return request(app.callback()) 32 | .get('/') 33 | .expect('setCreateInstanceHook') 34 | .expect(200); 35 | }); 36 | 37 | it('remove', () => { 38 | removeCreateInstanceHook(undefined); 39 | 40 | const hook = (_inst: any, _app: any, _ctx: any) => { 41 | return { 42 | sayHi() { 43 | return 'setCreateInstanceHook'; 44 | } 45 | }; 46 | }; 47 | setCreateInstanceHook(hook); 48 | removeCreateInstanceHook(hook); 49 | 50 | return request(app.callback()) 51 | .get('/') 52 | .expect('hi, egg') 53 | .expect(200); 54 | }); 55 | 56 | it('clear', () => { 57 | const hook = (_inst: any, _app: any, _ctx: any) => { 58 | return { 59 | sayHi() { 60 | return 'setCreateInstanceHook'; 61 | } 62 | }; 63 | }; 64 | setCreateInstanceHook(hook); 65 | clearCreateInstanceHook(); 66 | 67 | return request(app.callback()) 68 | .get('/') 69 | .expect('hi, egg') 70 | .expect(200); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-aop", 3 | "version": "0.5.3", 4 | "description": "aop for egg.", 5 | "main": "lib/index.js", 6 | "typings": "lib/index.d.ts", 7 | "files": [ 8 | "app/**/*.js", 9 | "app/**/*.d.ts", 10 | "config/**/*.js", 11 | "config/**/*.d.ts", 12 | "lib/**/*.js", 13 | "lib/**/*.d.ts", 14 | "typings/**/*.d.ts" 15 | ], 16 | "eggPlugin": { 17 | "name": "aop" 18 | }, 19 | "egg": { 20 | "typescript": true 21 | }, 22 | "dependencies": { 23 | "power-di": "^1.4.16", 24 | "tslib": "^1.9.0" 25 | }, 26 | "devDependencies": { 27 | "@types/mocha": "^5.1.0", 28 | "@types/node": "^9.6.5", 29 | "@types/supertest": "^2.0.4", 30 | "autod": "^3.0.1", 31 | "co": "^4.6.0", 32 | "egg-bin": "^4.7.0", 33 | "egg-ci": "^1.8.0", 34 | "egg-mock": "^3.17.0", 35 | "egg-ts-helper": "^1.6.0", 36 | "egg": "^2.7.1", 37 | "rimraf": "^2.6.2", 38 | "supertest": "^3.0.0", 39 | "tslib": "^1.9.0", 40 | "tslint": "^5.9.1", 41 | "typescript": "~2.8.1" 42 | }, 43 | "engines": { 44 | "node": ">=8.9.0" 45 | }, 46 | "scripts": { 47 | "test": "npm run lint -- --fix && npm run test-local", 48 | "test-local": "egg-bin test -r egg-ts-helper/register", 49 | "cov": "egg-bin cov -r egg-ts-helper/register", 50 | "lint": "tslint .", 51 | "ci": "npm run cov", 52 | "autod": "autod", 53 | "ts": "rimraf app/**/*.js lib/**/*.js && tsc", 54 | "prepublish": "npm run ts", 55 | "postpublish": "node scripts/published.js" 56 | }, 57 | "ci": { 58 | "version": "8" 59 | }, 60 | "repository": { 61 | "type": "git", 62 | "url": "https://github.com/zhang740/egg-aop.git" 63 | }, 64 | "eslintIgnore": [ 65 | "coverage" 66 | ], 67 | "keywords": [ 68 | "egg", 69 | "egg-aop", 70 | "ts", 71 | "aop" 72 | ], 73 | "author": "zhang740", 74 | "license": "MIT", 75 | "bugs": { 76 | "url": "https://github.com/zhang740/egg-aop/issues" 77 | }, 78 | "homepage": "https://github.com/zhang740/egg-aop#readme" 79 | } 80 | -------------------------------------------------------------------------------- /test/di.test.ts: -------------------------------------------------------------------------------- 1 | import { setCreateInstanceHook, clearCreateInstanceHook } from '../lib'; 2 | 3 | import * as request from 'supertest'; 4 | import mm from 'egg-mock'; 5 | import assert = require('power-assert'); 6 | 7 | describe('di normal', () => { 8 | let app: any; 9 | before(() => { 10 | app = mm.app({ 11 | baseDir: 'di', 12 | }); 13 | return app.ready(); 14 | }); 15 | 16 | after(() => app.close()); 17 | 18 | afterEach(mm.restore); 19 | 20 | it('normal lazyInject', () => { 21 | const insts: any[] = []; 22 | setCreateInstanceHook((inst) => { 23 | insts.push(inst); 24 | return inst; 25 | }); 26 | 27 | return request(app.callback()) 28 | .get('/') 29 | .expect('hi, egg') 30 | .expect(200) 31 | .then(() => { 32 | clearCreateInstanceHook(); 33 | assert.equal(insts.length, 1); 34 | }); 35 | }); 36 | 37 | it('normal inject', () => { 38 | return request(app.callback()) 39 | .get('/') 40 | .expect('hi, egg') 41 | .expect(200); 42 | }); 43 | 44 | it('without typescript metadata gen', () => { 45 | return request(app.callback()) 46 | .get('/notype') 47 | .expect('hi, egg') 48 | .expect(200); 49 | }); 50 | 51 | it('get component by method', () => { 52 | return request(app.callback()) 53 | .get('/getcomponent') 54 | .expect('hi, service') 55 | .expect(200); 56 | }); 57 | 58 | it('app component', async () => { 59 | await request(app.callback()) 60 | .get('/appcount') 61 | .expect('0') 62 | .expect(200); 63 | 64 | return request(app.callback()) 65 | .get('/appcount') 66 | .expect('1') 67 | .expect(200); 68 | }); 69 | 70 | it('get app component by method', () => { 71 | return request(app.callback()) 72 | .get('/appgetcomponent') 73 | .expect('2') 74 | .expect(200); 75 | }); 76 | 77 | it('mutli level lazyInject', () => { 78 | const insts: any[] = []; 79 | setCreateInstanceHook((inst) => { 80 | insts.push(inst); 81 | return inst; 82 | }); 83 | 84 | return request(app.callback()) 85 | .get('/mutli') 86 | .expect('hi, egg') 87 | .expect(200) 88 | .then(() => { 89 | clearCreateInstanceHook(); 90 | assert.equal(insts.length, 2); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /lib/decorators.ts: -------------------------------------------------------------------------------- 1 | import { inject as pdInject, lazyInject as pdLazyInject } from 'power-di/helper'; 2 | import { InstanceSource, getInstance } from './getInstance'; 3 | import { register as typeRegister } from './typeLoader'; 4 | import { getApp, getCtx } from './appctx'; 5 | import { getClsTypeByDecorator } from 'power-di/utils'; 6 | 7 | /** 8 | * register component 9 | * @export 10 | * @param {InstanceSource} from 'Context' | 'Application' 11 | * @param {*} [classType] register component 12 | * @param {*} [keyType] the key of register component, default component self 13 | * @returns void 14 | */ 15 | export function register(from: InstanceSource, classType?: any, keyType?: any) { 16 | return (target: any) => { 17 | const clsType = classType || target; 18 | typeRegister(clsType, keyType || clsType, from); 19 | }; 20 | } 21 | 22 | /** 23 | * inject in context 24 | * 25 | * @export 26 | * @param {*} [keyType] 27 | * @returns 28 | */ 29 | export function context(keyType?: any) { 30 | return register('Context', undefined, keyType); 31 | } 32 | 33 | /** 34 | * inject in application 35 | * 36 | * @export 37 | * @param {*} [keyType] 38 | * @returns 39 | */ 40 | export function application(keyType?: any) { 41 | return register('Application', undefined, keyType); 42 | } 43 | 44 | /** 45 | * inject 46 | * type: class or string 47 | */ 48 | export function inject(type?: any) { 49 | return (target: any, key: string) => { 50 | pdInject({ type })(target, key); 51 | const clsType = getClsTypeByDecorator(type, target, key); 52 | 53 | let value: any; 54 | Object.defineProperty(target, key, { 55 | configurable: true, 56 | get() { 57 | if (value !== undefined) { 58 | return value; 59 | } 60 | const ctx = getCtx(this); 61 | const app = getApp(this) || (ctx && ctx.app); 62 | value = getInstance(clsType, app, ctx); 63 | return target[key]; 64 | } 65 | }); 66 | }; 67 | } 68 | 69 | /** 70 | * lazy inject 71 | * type: class or string 72 | */ 73 | export function lazyInject(type?: any) { 74 | return (target: any, key: string) => { 75 | pdLazyInject({ type })(target, key); 76 | const clsType = getClsTypeByDecorator(type, target, key); 77 | 78 | const descriptor = Object.getOwnPropertyDescriptor(target, key); 79 | const defaultValue = descriptor && descriptor.value; 80 | Object.defineProperty(target, key, { 81 | configurable: true, 82 | get: function () { 83 | const ctx = getCtx(this); 84 | const app = getApp(this) || (ctx && ctx.app); 85 | const value = getInstance(clsType, app, ctx); 86 | return value !== undefined ? value : defaultValue; 87 | }, 88 | }); 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /lib/getInstance.ts: -------------------------------------------------------------------------------- 1 | import { IocContext, GetReturnType } from 'power-di'; 2 | import { getGlobalType } from 'power-di/utils'; 3 | import { setApp, setCtx, ctxSymbol } from './appctx'; 4 | import { typeLoader, register } from './typeLoader'; 5 | 6 | export type InstanceSource = 'Context' | 'Application'; 7 | 8 | export type CreateInstanceHookFunction = (instance: any, app: any, ctx?: any) => any; 9 | 10 | /** createInstanceHooks */ 11 | const ciHooks: CreateInstanceHookFunction[] = []; 12 | export function setCreateInstanceHook(func: CreateInstanceHookFunction) { 13 | ciHooks.push(func); 14 | } 15 | export function removeCreateInstanceHook(func: CreateInstanceHookFunction) { 16 | const index = ciHooks.indexOf(func); 17 | if (index >= 0) { 18 | ciHooks.splice(index, 1); 19 | } 20 | } 21 | export function clearCreateInstanceHook() { 22 | ciHooks.splice(0); 23 | } 24 | 25 | export const contextTypeSymbol = Symbol('contextType'); 26 | 27 | export function injectInstance(ioc: IocContext, inst: any, app: any, ctx: any) { 28 | ioc.inject(inst, (_globalType, typeCls) => { 29 | return getInstance(typeCls, app, ctx); 30 | }); 31 | } 32 | 33 | export function getInstance(clsType: KeyType, app: any, ctx?: any): GetReturnType { 34 | let ioc: IocContext = undefined; 35 | 36 | const { useCtxProxyForAppComponent, autoRegisterToCtx } = app.config.aop; 37 | 38 | if (autoRegisterToCtx && !typeLoader.has(clsType as any)) { 39 | register(clsType, clsType, (clsType as any)[contextTypeSymbol] || 'Context'); 40 | } 41 | 42 | const targetClsType = typeLoader.get(clsType); 43 | if (!targetClsType) { 44 | throw new Error(`ClassType [${getGlobalType(clsType)}] NOT found in typeLoader!`); 45 | } 46 | const from: InstanceSource = targetClsType[contextTypeSymbol]; 47 | 48 | switch (from) { 49 | case 'Application': 50 | ioc = useCtxProxyForAppComponent ? ctx.iocContext : app.iocContext; 51 | break; 52 | 53 | case 'Context': 54 | if (!ctx) { 55 | throw new Error(`inject [${getGlobalType(clsType)}] MUST in Context class instance.`); 56 | } 57 | ioc = ctx.iocContext; 58 | break; 59 | } 60 | 61 | let value: any = ioc.get(clsType); 62 | if (value) { return value; } 63 | 64 | switch (from) { 65 | case 'Application': 66 | if (useCtxProxyForAppComponent) { 67 | value = app.iocContext.get(clsType); 68 | 69 | if (!value) { 70 | value = new targetClsType(app); 71 | setApp(value, app); 72 | app.iocContext.register(value, clsType); 73 | injectInstance(app.iocContext, value, app, ctx); 74 | } 75 | 76 | value = new Proxy(value, { 77 | get(target, property) { 78 | if (property === ctxSymbol) { 79 | return ctx; 80 | } 81 | if (property === 'constructor') { 82 | return target[property]; 83 | } 84 | return typeof target[property] === 'function' ? target[property].bind(value) : target[property]; 85 | } 86 | }); 87 | 88 | value = ciHooks.reduce((pre, cur) => cur(pre, app), value); 89 | 90 | } else { 91 | value = ciHooks.reduce((pre, cur) => cur(pre, app), new targetClsType(app)); 92 | setApp(value, app); 93 | } 94 | break; 95 | 96 | case 'Context': 97 | value = ciHooks.reduce((pre, cur) => cur(pre, app, ctx), new targetClsType(ctx)); 98 | setCtx(value, ctx); 99 | break; 100 | } 101 | 102 | ioc.register(value, clsType as any); 103 | injectInstance(ioc, value, app, ctx); 104 | return value; 105 | } 106 | -------------------------------------------------------------------------------- /lib/aspect.ts: -------------------------------------------------------------------------------- 1 | const generatorFuncPrototype = Object.getPrototypeOf(function* (): any { }); 2 | function isGeneratorFunction(fn: any) { 3 | return typeof fn === 'function' && Object.getPrototypeOf(fn) === generatorFuncPrototype; 4 | } 5 | 6 | export type Throwable = Error | any; 7 | 8 | export const FunctionContextSymbol = Symbol('FunctionContextSymbol'); 9 | 10 | export interface FunctionContext { 11 | readonly inst: T; 12 | readonly functionName: string; 13 | args: any[]; 14 | ret: any; 15 | err: Error; 16 | } 17 | 18 | export interface AspectPoint { 19 | before?: (context: FunctionContext) => void; 20 | after?: (context: FunctionContext) => void; 21 | error?: (context: FunctionContext) => void; 22 | } 23 | 24 | function run(func: any, context: FunctionContext) { 25 | func && func(context); 26 | } 27 | 28 | function createContext(inst: any, fn: Function, args: any[]) { 29 | return { 30 | functionName: (fn as any).__name || fn.name, 31 | inst, 32 | args, 33 | } as FunctionContext; 34 | } 35 | 36 | export function funcWrapper(point: AspectPoint, fn: Function) { 37 | let newFn: any; 38 | 39 | if (isGeneratorFunction(fn)) { 40 | newFn = function* (...args: any[]) { 41 | const context = createContext(this, fn, args); 42 | try { 43 | run(point.before, context); 44 | context.ret = yield fn.apply(this, context.args); 45 | run(point.after, context); 46 | return context.ret; 47 | } catch (error) { 48 | context.err = error; 49 | run(point.error, context); 50 | if (context.err) { 51 | throw context.err; 52 | } 53 | } 54 | }; 55 | } else { 56 | // 非原生支持async的情况下没有有效方法判断async函数 57 | newFn = function (...args: any[]) { 58 | const context = createContext(this, fn, args); 59 | try { 60 | run(point.before, context); 61 | context.ret = fn.apply(this, context.args); 62 | if (context.ret instanceof Promise) { 63 | context.ret = context.ret.then((ret) => { 64 | context.ret = ret; 65 | run(point.after, context); 66 | return context.ret; 67 | }); 68 | if (point.error) { 69 | context.ret = (context.ret as Promise) 70 | .catch(error => { 71 | context.err = error; 72 | run(point.error, context); 73 | if (context.err) { 74 | throw context.err; 75 | } 76 | }); 77 | } 78 | Object.defineProperty(context.ret, FunctionContextSymbol, { 79 | enumerable: false, 80 | configurable: true, 81 | value: context, 82 | }); 83 | return context.ret; 84 | } else { 85 | run(point.after, context); 86 | return context.ret; 87 | } 88 | } catch (error) { 89 | context.err = error; 90 | run(point.error, context); 91 | if (context.err) { 92 | throw context.err; 93 | } 94 | } 95 | }; 96 | } 97 | 98 | newFn.__name = (fn as any).__name || fn.name; 99 | return newFn; 100 | } 101 | 102 | export function aspect(point: AspectPoint = {}) { 103 | return (target: any, key: string, descriptor: any) => { 104 | let fn = funcWrapper(point, descriptor.value || target[key]); 105 | 106 | const value = { 107 | configurable: true, 108 | get() { 109 | return fn; 110 | }, 111 | set(value: Function) { 112 | fn = funcWrapper(point, value); 113 | } 114 | }; 115 | Object.defineProperty(target, key, value); 116 | return value; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-aop 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![build status][travis-image]][travis-url] 5 | [![Test coverage][codecov-image]][codecov-url] 6 | [![David deps][david-image]][david-url] 7 | [![Known Vulnerabilities][snyk-image]][snyk-url] 8 | [![npm download][download-image]][download-url] 9 | 10 | [npm-image]: https://img.shields.io/npm/v/egg-aop.svg?style=flat-square 11 | [npm-url]: https://npmjs.org/package/egg-aop 12 | [travis-image]: https://img.shields.io/travis/eggjs/egg-aop.svg?style=flat-square 13 | [travis-url]: https://travis-ci.org/eggjs/egg-aop 14 | [codecov-image]: https://codecov.io/github/eggjs/egg-aop/coverage.svg?branch=master 15 | [codecov-url]: https://codecov.io/github/eggjs/egg-aop?branch=master 16 | [david-image]: https://img.shields.io/david/eggjs/egg-aop.svg?style=flat-square 17 | [david-url]: https://david-dm.org/eggjs/egg-aop 18 | [snyk-image]: https://snyk.io/test/npm/egg-aop/badge.svg?style=flat-square 19 | [snyk-url]: https://snyk.io/test/npm/egg-aop 20 | [download-image]: https://img.shields.io/npm/dm/egg-aop.svg?style=flat-square 21 | [download-url]: https://npmjs.org/package/egg-aop 22 | 23 | Add DI, AOP support for eggjs. 24 | 25 | ## DI 26 | 27 | ### Quick overview 28 | ```ts 29 | import { Service, Context } from 'egg'; 30 | import { context, lazyInject } from 'egg-aop'; 31 | 32 | @context() // or @application() 33 | export class TestService extends Service { 34 | get() { 35 | /* code */ 36 | } 37 | } 38 | 39 | export class Controller { 40 | @lazyInject() 41 | private testService: TestService; 42 | 43 | demo() { 44 | this.testService.get(); 45 | } 46 | } 47 | ``` 48 | 49 | ### API 50 | 51 | #### decorators 52 | - `@context(keyType?: any)` 53 | 54 | Declaration of life cycle of an instance, is context level. You can provide a class type or from metadata by TypeScript emit. 55 | 56 | - `@application(keyType?: any)` 57 | 58 | Declaration of life cycle of an instance, is context level. You can provide a class type or from metadata by TypeScript emit. 59 | 60 | - `@inject(keyType?: any)` 61 | 62 | Inject component when the class is instantiated. 63 | 64 | - `@lazyInject(keyType?: any)` 65 | 66 | Inject component when accessing the property. 67 | 68 | #### functions 69 | - `getInstance(clsType: any, app: any, ctx: any): T` 70 | 71 | You can use this function to manually get the component instance. 72 | 73 | - `setCreateInstanceHook(func: CreateInstanceHookFunction)` 74 | 75 | You can use this function to interception every new component instance. 76 | 77 | `type CreateInstanceHookFunction = (instance: any, app: any, ctx?: any) => any;` 78 | 79 | #### typeLoader 80 | 81 | `typeLoader` is an instance of `IocContext`, it stores all type's classes. You can use this to affect DI behavior. 82 | 83 | ## AOP 84 | 85 | ### Quick overview 86 | ```ts 87 | function logging(type: string) { 88 | return aspect({ 89 | // before method running 90 | before: (context) => { /* log code */ }, 91 | // after method running 92 | after: (context) => { /* log code */ }, 93 | // when method throw error 94 | onError: (context) => { /* log code */ }, 95 | }) 96 | } 97 | 98 | class DemoService { 99 | @logging('create') 100 | createData() { 101 | /* code */ 102 | } 103 | } 104 | 105 | /* FunctionContext type define */ 106 | export interface FunctionContext { 107 | readonly inst: T; 108 | readonly functionName: string; 109 | args: any[]; 110 | ret: any; 111 | err: Error; 112 | } 113 | ``` 114 | 115 | ### API 116 | 117 | #### functions 118 | - `aspect(point: AspectPoint = {})` 119 | 120 | You can use this to intercept method, this function provides `before` / `after` / `error` cross-sections. 121 | 122 | ```ts 123 | interface AspectPoint { 124 | before?: (context: FunctionContext) => void; 125 | after?: (context: FunctionContext) => void; 126 | error?: (context: FunctionContext) => void; 127 | } 128 | ``` 129 | 130 | The param `context` is the function's execution context. It includes `inst` / `args` / `ret`. You can replace them to affect the function execution. 131 | -------------------------------------------------------------------------------- /test/aspect.test.ts: -------------------------------------------------------------------------------- 1 | import { aspect } from '../lib'; 2 | import assert = require('power-assert'); 3 | const co = require('co'); 4 | 5 | async function assertThrowsAsync(fn: Function, regExp: RegExp) { 6 | let f = () => { }; 7 | try { 8 | await fn(); 9 | } catch (e) { 10 | f = () => { throw e; }; 11 | } finally { 12 | assert.throws(f, regExp); 13 | } 14 | } 15 | 16 | describe('test/aspect.test.js', () => { 17 | 18 | it('normal', () => { 19 | let a: A; 20 | let before = false; 21 | let after = false; 22 | let order: string[] = []; 23 | 24 | class A { 25 | @aspect({ 26 | before: (context) => { 27 | order.push('1'); 28 | before = true; 29 | assert.equal(context.inst, a); 30 | assert.deepEqual(context.args, ['test']); 31 | }, 32 | after: (context) => { 33 | order.push('3'); 34 | after = true; 35 | assert.equal(context.inst, a); 36 | assert.equal(context.ret, 'a:test'); 37 | } 38 | }) 39 | method(a: string) { 40 | order.push('2'); 41 | return `a:${a}`; 42 | } 43 | } 44 | 45 | a = new A; 46 | order.push('0'); 47 | a.method('test'); 48 | order.push('4'); 49 | assert.equal(before, true); 50 | assert.equal(after, true); 51 | assert.deepEqual(order, ['0', '1', '2', '3', '4']); 52 | }); 53 | 54 | it('async', async () => { 55 | let a: A; 56 | let before = false; 57 | let after = false; 58 | let order: string[] = []; 59 | 60 | function test() { 61 | return aspect({ 62 | before: (context) => { 63 | order.push('1'); 64 | before = true; 65 | assert.equal(context.inst, a); 66 | assert.deepEqual(context.args, ['test']); 67 | }, 68 | after: (context) => { 69 | order.push('3'); 70 | after = true; 71 | assert.equal(context.inst, a); 72 | assert.equal(context.ret, 'a:test'); 73 | } 74 | }); 75 | } 76 | 77 | class A { 78 | @test() 79 | async method(a: string) { 80 | order.push('2'); 81 | return `a:${a}`; 82 | } 83 | } 84 | 85 | a = new A; 86 | order.push('0'); 87 | await a.method('test'); 88 | order.push('4'); 89 | assert.equal(before, true); 90 | assert.equal(after, true); 91 | assert.deepEqual(order, ['0', '1', '2', '3', '4']); 92 | }); 93 | 94 | it('generator', async () => { 95 | let a: A; 96 | let before = false; 97 | let after = false; 98 | let order: string[] = []; 99 | 100 | function test() { 101 | return aspect({ 102 | before: (context) => { 103 | order.push('1'); 104 | before = true; 105 | assert.equal(context.inst, a); 106 | assert.deepEqual(context.args, ['test']); 107 | }, 108 | after: (context) => { 109 | order.push('3'); 110 | after = true; 111 | assert.equal(context.inst, a); 112 | assert.equal(context.ret, 'a:test'); 113 | } 114 | }); 115 | } 116 | 117 | class A { 118 | @test() 119 | * method(a: string) { 120 | order.push('2'); 121 | return `a:${a}`; 122 | } 123 | } 124 | 125 | a = new A; 126 | order.push('0'); 127 | await co(a.method('test')); 128 | order.push('4'); 129 | assert.equal(before, true); 130 | assert.equal(after, true); 131 | assert.deepEqual(order, ['0', '1', '2', '3', '4']); 132 | }); 133 | 134 | it('throw error', () => { 135 | let a: A; 136 | let error = false; 137 | 138 | class A { 139 | @aspect({ 140 | error: (context) => { 141 | assert.equal(context.inst, a); 142 | error = true; 143 | } 144 | }) 145 | method(a: string) { 146 | throw new Error(a); 147 | } 148 | } 149 | 150 | a = new A; 151 | assert.throws(() => a.method('test')); 152 | assert.equal(error, true); 153 | }); 154 | 155 | it('async, throw error', async () => { 156 | let a: A; 157 | let error = false; 158 | 159 | class A { 160 | @aspect({ 161 | error: (context) => { 162 | assert.equal(context.inst, a); 163 | error = true; 164 | } 165 | }) 166 | async method(a: string) { 167 | throw new Error(a); 168 | } 169 | } 170 | 171 | a = new A; 172 | await assertThrowsAsync(async () => await a.method('test'), /Error/); 173 | assert.equal(error, true); 174 | }); 175 | 176 | it('replace args', () => { 177 | class A { 178 | @aspect({ 179 | before: (context) => { 180 | assert.deepEqual(context.args, ['test']); 181 | context.args = ['changeargs']; 182 | }, 183 | }) 184 | method(a: string) { 185 | return `a:${a}`; 186 | } 187 | } 188 | 189 | assert.equal(new A().method('test'), 'a:changeargs'); 190 | }); 191 | 192 | it('replace ret', () => { 193 | class A { 194 | @aspect({ 195 | after: (context) => { 196 | assert.equal(context.ret, 'a:test'); 197 | context.ret = 'changeret'; 198 | }, 199 | }) 200 | method(a: string) { 201 | return `a:${a}`; 202 | } 203 | } 204 | 205 | assert.equal(new A().method('test'), 'changeret'); 206 | }); 207 | 208 | it('catch error', () => { 209 | class A { 210 | @aspect({ 211 | error: (context) => { 212 | context.err = undefined; 213 | }, 214 | }) 215 | method(a: string) { 216 | return `a:${a}`; 217 | } 218 | } 219 | 220 | assert.doesNotThrow(() => new A().method('test')); 221 | }); 222 | 223 | it('replace error', () => { 224 | let testErr = new Error('test'); 225 | 226 | class A { 227 | @aspect({ 228 | error: (context) => { 229 | context.err = testErr; 230 | }, 231 | }) 232 | method(a: string) { 233 | throw new Error(a); 234 | } 235 | } 236 | 237 | assert.throws(() => new A().method('test'), (err: Error) => { 238 | return err === testErr; 239 | }); 240 | }); 241 | 242 | }); 243 | --------------------------------------------------------------------------------