Repository: artusjs/core
Pull request #12 (2022-03-18 13:53:29)

feat(lifecycle): better application hook decorator


Patch:
diff --git a/src/constraints.ts b/src/constraints.ts
index 731e29f..a3c2b39 100644
--- a/src/constraints.ts
+++ b/src/constraints.ts
@@ -2,3 +2,5 @@ export const DEFAULT_LOADER = 'module';
 
 export const ARTUS_DEFAULT_CONTAINER = 'artus#default_container';
 export const ARTUS_TRIGGER_ID = 'artus#trigger';
+
+export const HOOK_META_SYMBOL = Symbol('HookMeta');
diff --git a/src/decorator.ts b/src/decorator.ts
index 9af941c..9b22028 100644
--- a/src/decorator.ts
+++ b/src/decorator.ts
@@ -1,11 +1,26 @@
+// import { Injectable } from '@artus/injection';
+import { artusContainer, getArtusApplication } from '.';
+import { HOOK_META_SYMBOL } from './constraints';
 import { Application } from './types';
 
-export function ApplicationHook(app: Application, hookName?: string): PropertyDecorator {
+export function ApplicationHookClass(): ClassDecorator {
+  return (target: any) => {
+    // TODO: 待替换为 injection 提供的属性方法
+    target.constructor.prototype.app = getArtusApplication();
+    artusContainer.set({
+      type: target
+    });
+  };
+};
+
+export function ApplicationHook(hookName?: string): PropertyDecorator {
   return (target: any, propertyKey: string|symbol) => {
     if (typeof propertyKey === 'symbol') {
       throw new Error(`hookName is not support symbol [${propertyKey.description}]`);
     }
+    const app: Application = getArtusApplication();
     const hookFn = target[propertyKey];
+    hookFn[HOOK_META_SYMBOL] = target.constructor;
     app.registerHook(hookName ?? propertyKey, hookFn);
   };
 };
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 739eb9b..f6c58c6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,6 +18,10 @@ import { Container } from '@artus/injection';
 import { ArtusApplication } from './application';
 import { ARTUS_DEFAULT_CONTAINER } from './constraints';
 import { Trigger } from './trigger';
+
 export const artusContainer = new Container(ARTUS_DEFAULT_CONTAINER);
+
 artusContainer.set({ type: ArtusApplication });
 artusContainer.set({ type: Trigger });
+
+export const getArtusApplication = () => artusContainer.get(ArtusApplication);
diff --git a/src/lifecycle/index.ts b/src/lifecycle/index.ts
index 7054cf8..0fc4a18 100644
--- a/src/lifecycle/index.ts
+++ b/src/lifecycle/index.ts
@@ -1,3 +1,5 @@
+import { artusContainer } from '..';
+import { HOOK_META_SYMBOL } from '../constraints';
 import { Application } from '../types';
 
 export type HookFunction = <T = unknown>(hookProps : {
@@ -16,6 +18,8 @@ export class LifecycleManager {
     'beforeClose' // 应用即将关闭
   ];
   hookFnMap: Map<string, HookFunction[]> = new Map();
+
+  // @ts-expect-error
   private app: Application;
 
   constructor(app: Application) {
@@ -50,8 +54,15 @@ export class LifecycleManager {
       return;
     }
     for (const hookFn of fnList) {
-      await hookFn<T>({
-        app: this.app,
+      let that: any;
+      if (hookFn[HOOK_META_SYMBOL]) {
+        try {
+          that = artusContainer.get(hookFn[HOOK_META_SYMBOL]);
+        } catch (error) {
+          // SEEME: 临时行为，待 injection 的 get 提供参数允许 id 不存在
+        }
+      }
+      await hookFn.call(that, {
         lifecycleManager: this,
         payload
       });


Test patch:
diff --git a/test/app.test.ts b/test/app.test.ts
index fdca8d8..ac6db2c 100644
--- a/test/app.test.ts
+++ b/test/app.test.ts
@@ -1,15 +1,16 @@
 import 'reflect-metadata';
 import axios from 'axios';
 import assert from 'assert';
+import { getArtusApplication } from '../src';
 
 describe('test/app.test.ts', () => {
   describe('app koa with ts', () => {
     it('should run app', async () => {
       const {
         main,
-        app,
         isListening
       } = await import('./fixtures/app-koa-with-ts/app');
+      const app = getArtusApplication();
       await main();
       const testResponse = await axios.get('http://127.0.0.1:3000');
       assert(testResponse.status === 200);
diff --git a/test/fixtures/app-koa-with-ts/app.ts b/test/fixtures/app-koa-with-ts/app.ts
index 7328feb..1483ba6 100644
--- a/test/fixtures/app-koa-with-ts/app.ts
+++ b/test/fixtures/app-koa-with-ts/app.ts
@@ -1,30 +1,32 @@
 import { Server } from 'http';
 import Koa from 'koa';
-import { artusContainer, ArtusApplication } from '../../../src';
-import { ApplicationHook } from '../../../src/decorator';
+import { getArtusApplication } from '../../../src';
+import { ApplicationHook, ApplicationHookClass } from '../../../src/decorator';
 
-const app = artusContainer.get(ArtusApplication);
 let server: Server;
 const koaApp = new Koa();
 
-koaApp.use((ctx) => {
-  ctx.status = 200;
-  ctx.body = 'Hello Artus';
-});
-
+@ApplicationHookClass()
 export class ApplicationHookExtension {
-  @ApplicationHook(app)
+  testStr: string = 'Hello Artus';
+
+  @ApplicationHook()
   willReady() {
+    koaApp.use((ctx) => {
+      ctx.status = 200;
+      ctx.body = this.testStr;
+    });
     server = koaApp.listen(3000);
   }
 
-  @ApplicationHook(app)
+  @ApplicationHook()
   beforeClose() {
     server?.close();
   }
 }
 
 async function main() {
+  const app = getArtusApplication();
   await app.load({
     rootDir: __dirname,
     items: []
@@ -36,6 +38,5 @@ const isListening = () => server.listening;
 
 export {
   main,
-  app,
   isListening
 };
diff --git a/test/fixtures/trigger-http/app.ts b/test/fixtures/trigger-http/app.ts
index fbf3d49..525e21f 100644
--- a/test/fixtures/trigger-http/app.ts
+++ b/test/fixtures/trigger-http/app.ts
@@ -1,42 +1,48 @@
 import { Server } from 'http';
-import { artusContainer, ArtusApplication } from '../../../src';
-import { ApplicationHook } from '../../../src/decorator';
+import { Inject } from '@artus/injection';
+import { artusContainer, ArtusApplication, getArtusApplication } from '../../../src';
+import { ApplicationHook, ApplicationHookClass } from '../../../src/decorator';
 import { HttpTrigger } from './httpTrigger';
 import http from 'http';
 import { Context, Input } from '@artus/pipeline';
 
 artusContainer.set({ type: HttpTrigger });
-const app: ArtusApplication = artusContainer.get(ArtusApplication);
 let server: Server;
 
+@ApplicationHookClass()
 export class ApplicationHookExtension {
-  @ApplicationHook(app)
+  @Inject(ArtusApplication)
+  // @ts-ignore
+  app: ArtusApplication;
+
+  @ApplicationHook()
   async didLoad() {
-    app.trigger.use(async (ctx: Context) => {
+    this.app.trigger.use(async (ctx: Context) => {
       const { data } = ctx.output;
       data.content = { title: 'Hello Artus' };
     });
   }
 
-  @ApplicationHook(app)
+  @ApplicationHook()
   willReady() {
     server = http
       .createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {
         const input = new Input();
         input.params = { req, res };
-        await app.trigger.init(input);
-        await app.trigger.startPipeline();
+        await this.app.trigger.init(input);
+        await this.app.trigger.startPipeline();
       })
       .listen(3001)
   }
 
-  @ApplicationHook(app)
+  @ApplicationHook()
   beforeClose() {
     server?.close();
   }
 }
 
 async function main() {
+  const app: ArtusApplication = getArtusApplication();
   await app.load({
     rootDir: __dirname,
     items: []
@@ -48,6 +54,5 @@ const isListening = () => server.listening;
 
 export {
   main,
-  app,
   isListening
 };
diff --git a/test/fixtures/trigger-http/tsconfig.json b/test/fixtures/trigger-http/tsconfig.json
new file mode 100644
index 0000000..2aa8781
--- /dev/null
+++ b/test/fixtures/trigger-http/tsconfig.json
@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "esModuleInterop": true,
+    "experimentalDecorators": true
+  },
+  "rootDir": "./",
+  "exclude": []
+}
\ No newline at end of file
diff --git a/test/trigger.test.ts b/test/trigger.test.ts
index 6c71465..fed9834 100644
--- a/test/trigger.test.ts
+++ b/test/trigger.test.ts
@@ -1,6 +1,7 @@
 import 'reflect-metadata';
 import axios from 'axios';
 import assert from 'assert';
+import { getArtusApplication } from '../src';
 
 describe('test/trigger.test.ts', () => {
   describe('trigger with http', () => {
@@ -9,9 +10,9 @@ describe('test/trigger.test.ts', () => {
       try {
         const {
           main,
-          app,
           isListening
         } = await import('./fixtures/trigger-http/app');
+        const app = getArtusApplication();
         await main();
         const testResponse = await axios.get('http://127.0.0.1:3001');
         assert(testResponse.status === 200);