diff --git a/README.md b/README.md index dcafdc6..73cb37b 100644 --- a/README.md +++ b/README.md @@ -19,22 +19,67 @@ A Provider is a abstract concept. Maybe an Entity, a File, a structure, a compon ### Usage ```javascript -import { boot, Application } from '@augejs/module-core'; +import { Module, Config, Value, boot, ILogger, GetLogger } from '@augejs/module-core'; + +@Module() +@Config({ + fullName: "augejs awesome~", + hello: { + name: 'augejs', + age: 12, + } +}) +class Module1 { + @Value('hello') + testName!:string; + + @Value('fullName') + fullName!:string; + + @Value('hello.age') + age!:number; + + @GetLogger() + logger!:ILogger; -@Application() -export class AppModule { - onInit() { - console.log('AppModule onInit '); + async onInit() { + this.logger.info('Module1 onInit'); + this.logger.info(`config: hello: ${JSON.stringify(this.testName)}`); + this.logger.info(`config fullName: ${this.fullName}`); + this.logger.info(`config age: ${this.age}`); } - onAppDidReady(context:any) { - console.log('AppModule onAppDidReady '); +} + +@Module() +class Module2 { + @GetLogger() + logger!:ILogger; + + async onInit() { + this.logger.info('Module2 onInit'); + } +} + +@Module({ + subModules: [ + Module1, [Module2] + ] +}) +class AppModule { + + @GetLogger() + logger!:ILogger; + + async onInit() { + this.logger.info('AppModule onInit'); } } (async () => { - await boot(AppModule); + await boot(AppModule); })(); + ``` diff --git a/package-lock.json b/package-lock.json index 6c9d3c5..ffd13d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "@augejs/module-core", - "version": "0.0.18", + "version": "0.0.30", "lockfileVersion": 1, "requires": true, "dependencies": { "@augejs/provider-scanner": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/@augejs/provider-scanner/-/provider-scanner-0.0.24.tgz", - "integrity": "sha512-ddBmFp2pJ9vwm9IvZo6tnupua4tf80vdNMoUG5g9ZGKZUKWaOTwszqibIh14TogmvNs7MEONIMWb2ijk4/AEIQ==", + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@augejs/provider-scanner/-/provider-scanner-0.0.34.tgz", + "integrity": "sha512-axldjI6t1cpsYEHO+AzeZI/ksm/euqES2Olyf83OeisxtAKscgt78N8qYyDQkbWKKbSBy9DjRhayHurY0ak30w==", "requires": { "extend": "^3.0.2" } @@ -348,6 +348,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", @@ -411,6 +420,248 @@ "minimist": "^1.2.0" } }, + "@commitlint/ensure": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-11.0.0.tgz", + "integrity": "sha512-/T4tjseSwlirKZdnx4AuICMNNlFvRyPQimbZIOYujp9DSO6XRtOy9NrmvWujwHsq9F5Wb80QWi4WMW6HMaENug==", + "dev": true, + "requires": { + "@commitlint/types": "^11.0.0", + "lodash": "^4.17.19" + } + }, + "@commitlint/execute-rule": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-11.0.0.tgz", + "integrity": "sha512-g01p1g4BmYlZ2+tdotCavrMunnPFPhTzG1ZiLKTCYrooHRbmvqo42ZZn4QMStUEIcn+jfLb6BRZX3JzIwA1ezQ==", + "dev": true + }, + "@commitlint/format": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-11.0.0.tgz", + "integrity": "sha512-bpBLWmG0wfZH/svzqD1hsGTpm79TKJWcf6EXZllh2J/LSSYKxGlv967lpw0hNojme0sZd4a/97R3qA2QHWWSLg==", + "dev": true, + "requires": { + "@commitlint/types": "^11.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@commitlint/is-ignored": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-11.0.0.tgz", + "integrity": "sha512-VLHOUBN+sOlkYC4tGuzE41yNPO2w09sQnOpfS+pSPnBFkNUUHawEuA44PLHtDvQgVuYrMAmSWFQpWabMoP5/Xg==", + "dev": true, + "requires": { + "@commitlint/types": "^11.0.0", + "semver": "7.3.2" + } + }, + "@commitlint/lint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-11.0.0.tgz", + "integrity": "sha512-Q8IIqGIHfwKr8ecVZyYh6NtXFmKw4YSEWEr2GJTB/fTZXgaOGtGFZDWOesCZllQ63f1s/oWJYtVv5RAEuwN8BQ==", + "dev": true, + "requires": { + "@commitlint/is-ignored": "^11.0.0", + "@commitlint/parse": "^11.0.0", + "@commitlint/rules": "^11.0.0", + "@commitlint/types": "^11.0.0" + } + }, + "@commitlint/load": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-11.0.0.tgz", + "integrity": "sha512-t5ZBrtgvgCwPfxmG811FCp39/o3SJ7L+SNsxFL92OR4WQxPcu6c8taD0CG2lzOHGuRyuMxZ7ps3EbngT2WpiCg==", + "dev": true, + "requires": { + "@commitlint/execute-rule": "^11.0.0", + "@commitlint/resolve-extends": "^11.0.0", + "@commitlint/types": "^11.0.0", + "chalk": "4.1.0", + "cosmiconfig": "^7.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@commitlint/message": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-11.0.0.tgz", + "integrity": "sha512-01ObK/18JL7PEIE3dBRtoMmU6S3ecPYDTQWWhcO+ErA3Ai0KDYqV5VWWEijdcVafNpdeUNrEMigRkxXHQLbyJA==", + "dev": true + }, + "@commitlint/parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-11.0.0.tgz", + "integrity": "sha512-DekKQAIYWAXIcyAZ6/PDBJylWJ1BROTfDIzr9PMVxZRxBPc1gW2TG8fLgjZfBP5mc0cuthPkVi91KQQKGri/7A==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-parser": "^3.0.0" + } + }, + "@commitlint/read": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-11.0.0.tgz", + "integrity": "sha512-37V0V91GSv0aDzMzJioKpCoZw6l0shk7+tRG8RkW1GfZzUIytdg3XqJmM+IaIYpaop0m6BbZtfq+idzUwJnw7g==", + "dev": true, + "requires": { + "@commitlint/top-level": "^11.0.0", + "fs-extra": "^9.0.0", + "git-raw-commits": "^2.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + } + } + }, + "@commitlint/resolve-extends": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-11.0.0.tgz", + "integrity": "sha512-WinU6Uv6L7HDGLqn/To13KM1CWvZ09VHZqryqxXa1OY+EvJkfU734CwnOEeNlSCK7FVLrB4kmodLJtL1dkEpXw==", + "dev": true, + "requires": { + "import-fresh": "^3.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@commitlint/rules": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-11.0.0.tgz", + "integrity": "sha512-2hD9y9Ep5ZfoNxDDPkQadd2jJeocrwC4vJ98I0g8pNYn/W8hS9+/FuNpolREHN8PhmexXbkjrwyQrWbuC0DVaA==", + "dev": true, + "requires": { + "@commitlint/ensure": "^11.0.0", + "@commitlint/message": "^11.0.0", + "@commitlint/to-lines": "^11.0.0", + "@commitlint/types": "^11.0.0" + } + }, + "@commitlint/to-lines": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-11.0.0.tgz", + "integrity": "sha512-TIDTB0Y23jlCNubDROUVokbJk6860idYB5cZkLWcRS9tlb6YSoeLn1NLafPlrhhkkkZzTYnlKYzCVrBNVes1iw==", + "dev": true + }, + "@commitlint/top-level": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-11.0.0.tgz", + "integrity": "sha512-O0nFU8o+Ws+py5pfMQIuyxOtfR/kwtr5ybqTvR+C2lUPer2x6lnQU+OnfD7hPM+A+COIUZWx10mYQvkR3MmtAA==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "@commitlint/types": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-11.0.0.tgz", + "integrity": "sha512-VoNqai1vR5anRF5Tuh/+SWDFk7xi7oMwHrHrbm1BprYXjB2RJsWLhUrStMssDxEl5lW/z3EUdg8RvH/IUBccSQ==", + "dev": true + }, "@eslint/eslintrc": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.0.tgz", @@ -1713,6 +1964,59 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "commitlint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-11.0.0.tgz", + "integrity": "sha512-nTmP1tM52gfi39tDCN8dAlRRWJyVoJY2JuYgVhSONETGJ2MY69K/go0YbCzlIEDO/bUka5ybeI6CJz5ZicvNzg==", + "dev": true, + "requires": { + "@commitlint/cli": "^11.0.0" + }, + "dependencies": { + "@commitlint/cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-11.0.0.tgz", + "integrity": "sha512-YWZWg1DuqqO5Zjh7vUOeSX76vm0FFyz4y0cpGMFhrhvUi5unc4IVfCXZ6337R9zxuBtmveiRuuhQqnRRer+13g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.11.2", + "@commitlint/format": "^11.0.0", + "@commitlint/lint": "^11.0.0", + "@commitlint/load": "^11.0.0", + "@commitlint/read": "^11.0.0", + "chalk": "4.1.0", + "core-js": "^3.6.1", + "get-stdin": "8.0.0", + "lodash": "^4.17.19", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0", + "yargs": "^15.1.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -2074,6 +2378,12 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3613,6 +3923,15 @@ "is-glob": "^4.0.1" } }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -6547,6 +6866,12 @@ "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", "dev": true }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -6710,6 +7035,15 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "requires": { + "global-dirs": "^0.1.1" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", diff --git a/package.json b/package.json index 5317a65..d5ef770 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@augejs/module-core", - "version": "0.0.18", + "version": "0.0.31", "description": "`@augejs/module-core` is a module framework which support using `dependency injection` way to composite kinds of `Modules` and `Providers` to complex application.", "main": "dist/main.js", "directories": { @@ -31,7 +31,7 @@ }, "homepage": "https://github.com/augejs/module-core#readme", "dependencies": { - "@augejs/provider-scanner": "^0.0.24", + "@augejs/provider-scanner": "^0.0.34", "extend": "^3.0.2", "inversify": "^5.0.1", "object-path": "^0.11.4", @@ -41,6 +41,7 @@ "@types/extend": "^3.0.1", "@types/jest": "^26.0.10", "@types/object-path": "^0.11.0", + "commitlint": "^11.0.0", "conventional-changelog-cli": "^2.1.0", "eslint": "^7.7.0", "eslint-config-prettier": "^6.11.0", diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index aafc9ba..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const ScanInputKeys = { - Container: 'Container', - ProcessArgs: 'ProcessArgs', - LifeCyclePhase: 'LifeCyclePhase', -}; - -export const ScanOutputKeys = { - InstanceFactory: 'InstanceFactory', - Instance: 'Instance', - InstanceConfig: 'InstanceConfig', - RuntimeConfig: 'RuntimeConfig', -}; diff --git a/src/decorators/Application.decorator.test.ts b/src/decorators/Application.decorator.test.ts deleted file mode 100644 index 66a89c5..0000000 --- a/src/decorators/Application.decorator.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Application } from './Application.decorator'; - -describe('decorators: App.decorator', () => { - it('App.decorator is actually a Module.decorator', async () => { - - @Application() - class AppModule {} - - expect(1).toBe(1); - }) -}); diff --git a/src/decorators/Application.decorator.ts b/src/decorators/Application.decorator.ts deleted file mode 100644 index a4fb803..0000000 --- a/src/decorators/Application.decorator.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { argv } from 'yargs'; -import { HookMetadata, hookUtil, IScanNode, Metadata } from '@augejs/provider-scanner'; -import { Module, IModuleOptions } from './Module.decorator'; -import { ScanHook } from './ScanHook.decorator'; -import { Container, BindingScopeEnum } from '../ioc'; -import { ScanInputKeys, ScanOutputKeys } from '../constants'; -import { getConfigAccessPath, objectExtend, objectPath } from '../utils'; -import { ILogger, Logger } from '../logger'; - -const logger:ILogger = Logger.getLogger('App'); - -type LifeCycleOptions = { - [lifeCyclePhase: string]: string[] -} - -const DefaultLifeCycleConfig: LifeCycleOptions = -{ - startupLifecyclePhase: [ - 'onInit', 'onAppWillReady', '__onAppReady__' - ], - - readyLifecyclePhase: [ - 'onAppDidReady', - ], - - shutdownLifecyclePhase: [ - 'onAppWillClose', - ] -}; - -export interface IAppOptions extends IModuleOptions { - containerOptions?:object -} - -export function Application (options?: IAppOptions):ClassDecorator { - return function(target: Function) { - const containerOptions = { - defaultScope: BindingScopeEnum.Singleton, - autoBindInjectable: false, - skipBaseClassChecks: true, - ...(options?.containerOptions || {}) - }; - Metadata.decorate([ - Module({ - providers: options?.providers, - subModules: options?.subModules, - hooks: options?.hooks, - }), - ScanHook([ - createInitEnvironmentHook(containerOptions), - createLifeCycleHook(DefaultLifeCycleConfig), - ]), - ], target); - } -} - -function createInitEnvironmentHook(containerOptions:object):Function { - return async function (scanNode: IScanNode, next: Function) { - // set for ioc container - scanNode.context!.inputs.set(ScanInputKeys.Container, new Container(containerOptions)); - // set for process args - scanNode.context!.inputs.set(ScanInputKeys.ProcessArgs, argv); - - scanNode.context!.inputs.set(ScanInputKeys.LifeCyclePhase, {}); - - const runtimeConfig:object = {}; - scanNode.context!.outputs.set(ScanOutputKeys.RuntimeConfig, runtimeConfig); - - await next(); - - objectExtend(true, runtimeConfig, argv); - } -} - -function createLifeCycleHook(lifeCycleOptions:LifeCycleOptions):Function { - return async function lifeCycleHook(scanNode: IScanNode, next: Function) { - const lifeCyclePhase:any = scanNode.context!.inputs.get(ScanInputKeys.LifeCyclePhase); - Object.keys(lifeCycleOptions).forEach((lifeCyclePhaseName: string) => { - lifeCyclePhase[lifeCyclePhaseName] = {}; - }); - - await next(); - - const lifeCyclePhasesHookMap:any = {}; - Object.keys(lifeCycleOptions).forEach((lifeCyclePhaseName: string) => { - const lifeCycleNames:string[] = lifeCycleOptions[lifeCyclePhaseName]; - const childrenHook:Function = hookUtil.sequenceHooks(lifeCycleNames.map((lifeCycleName:string) => { - return buildScanNodeInstanceLifeCycleHook(scanNode, lifeCycleName); - })); - const selfHook:Function = hookUtil.parallelHooks(HookMetadata.getMetadata(lifeCyclePhase[lifeCyclePhaseName])); - lifeCyclePhasesHookMap[lifeCyclePhaseName] = hookUtil.sequenceHooks([ - childrenHook, - selfHook - ]); - }); - - // startup - try { - await lifeCyclePhasesHookMap.startupLifecyclePhase(scanNode); - } catch(err:any) { - logger.error('StartUp Error \n' + err); - } - - // after startup - process.nextTick(()=>{ - (async () => { - try { - await lifeCyclePhasesHookMap.readyLifecyclePhase(scanNode); - } catch(err:any) { - logger.error('StartUp Error \n' + err); - } - })(); - }); - - //shutdown - // https://hackernoon.com/graceful-shutdown-in-nodejs-2f8f59d1c357 - // https://blog.risingstack.com/graceful-shutdown-node-js-kubernetes/ - process.on('exit', () => { - (async () => { - try { - await lifeCyclePhasesHookMap.shutdownLifecyclePhase(scanNode); - } catch(err:any) { - logger.error('ShutDown Error \n' + err); - } - })() - }) - } -} - -function buildScanNodeInstanceLifeCycleHook(scanNode: IScanNode, lifecycleName: string):Function { - let selfLifeCycleHook:Function = hookUtil.noopHook; - let instance:any = scanNode.outputs.get(ScanOutputKeys.Instance); - if (instance === undefined) { - const instanceFactory:Function | undefined = scanNode.outputs.get(ScanOutputKeys.InstanceFactory); - if (instanceFactory) { - // set the instance config - const configAccessPath:string = getConfigAccessPath(scanNode.namePaths); - const runtimeConfig:object = scanNode.context!.outputs.get(ScanOutputKeys.RuntimeConfig); - const scanNodeConfig:any = objectPath.get(runtimeConfig, configAccessPath); - scanNode.outputs.set(ScanOutputKeys.InstanceConfig, scanNodeConfig); - instance = instanceFactory(); - } else { - instance = null; - } - scanNode.outputs.set(ScanOutputKeys.Instance, instance); - } - - // add self life cycle - if (instance) { - // keep the reference to scanNode - instance.$scanNode = scanNode; - // bind the life cycle - if(typeof instance[lifecycleName] === 'function') { - selfLifeCycleHook = (instance[lifecycleName] as Function).bind(instance); - } - } - - // build the children life cycle - const childrenLifeCycleHook:Function = hookUtil.parallelHooks( - scanNode.children.map((child:IScanNode) => { - return buildScanNodeInstanceLifeCycleHook(child, lifecycleName); - }) - ) - - // children life cycle first - return hookUtil.bindHookContext(scanNode, hookUtil.sequenceHooks([ - childrenLifeCycleHook, - selfLifeCycleHook, - ])); -} diff --git a/src/decorators/ChildrenHooksCompositeFunction.decorator.ts b/src/decorators/ChildrenHooksCompositeFunction.decorator.ts new file mode 100644 index 0000000..213545f --- /dev/null +++ b/src/decorators/ChildrenHooksCompositeFunction.decorator.ts @@ -0,0 +1,16 @@ +import { ChildrenHooksCompositeFunctionMetadata } from '@augejs/provider-scanner'; + +export function ChildrenHooksCompositeFunction(fn: Function): ClassDecorator { + return function(target: Function) { + ChildrenHooksCompositeFunction.defineMetadata(target, fn); + } +} + +ChildrenHooksCompositeFunction.defineMetadata = (target: object, fn: Function) => { + ChildrenHooksCompositeFunctionMetadata.defineMetadata(target, fn); +} + +ChildrenHooksCompositeFunction.getMetadata = (target: object): Function => { + return ChildrenHooksCompositeFunctionMetadata.getMetadata(target); +} + diff --git a/src/decorators/ConfigLoader.decorator.ts b/src/decorators/ConfigLoader.decorator.ts index 64ca866..c730471 100644 --- a/src/decorators/ConfigLoader.decorator.ts +++ b/src/decorators/ConfigLoader.decorator.ts @@ -1,6 +1,7 @@ -import { IScanNode, Metadata } from '@augejs/provider-scanner'; +import { Metadata } from '@augejs/provider-scanner'; +import { IScanNode } from '../utils'; -const noopLoader = (config:any) => config; +const noopLoader = () => {}; export function ConfigLoader (loader: (config:object, scanNode:IScanNode)=>Promise):ClassDecorator { return function(target: Function) { diff --git a/src/decorators/GetLogger.decorator.ts b/src/decorators/GetLogger.decorator.ts index f6bc22f..f7a5ba4 100644 --- a/src/decorators/GetLogger.decorator.ts +++ b/src/decorators/GetLogger.decorator.ts @@ -1,5 +1,6 @@ -import { IScanNode, NameMetadata } from "@augejs/provider-scanner"; +import { NameMetadata } from '@augejs/provider-scanner'; import { ILogger, Logger } from "../logger"; +import { IScanNode } from '../utils'; const noopObject = {}; export function GetLogger(context:string = ''):PropertyDecorator { @@ -9,12 +10,13 @@ export function GetLogger(context:string = ''):PropertyDecorator { const descriptor:PropertyDescriptor = { get():ILogger { - if ((this as any)[memoizedName] === noopObject) { - const scanNode:IScanNode = (this as any).$scanNode; + const instance:any = this; + if (instance[memoizedName] === noopObject) { + const scanNode:IScanNode = instance.$scanNode; const logger:ILogger = Logger.getLogger(context || NameMetadata.getMetadata(scanNode.provider)); - (this as any)[memoizedName] = logger; + instance[memoizedName] = logger; } - return (this as any)[memoizedName] as ILogger; + return instance[memoizedName] as ILogger; }, }; diff --git a/src/decorators/LifeCyclePhaseHook.decorator.ts b/src/decorators/LifeCyclePhaseHook.decorator.ts index 2fa676a..3dee60b 100644 --- a/src/decorators/LifeCyclePhaseHook.decorator.ts +++ b/src/decorators/LifeCyclePhaseHook.decorator.ts @@ -1,13 +1,14 @@ -import { IScanNode, Metadata, hookUtil, HookMetadata } from '@augejs/provider-scanner'; +import { Metadata, hookUtil, HookMetadata } from '@augejs/provider-scanner'; import { ScanHook } from './ScanHook.decorator'; -import { ScanInputKeys } from '../constants'; +import { IScanNode } from '../utils'; + export function LifeCyclePhaseHook (lifeCyclePhase:string, hooks: Function | Function[]):ClassDecorator { return function(target: Function) { Metadata.decorate([ ScanHook( async (scanNode: IScanNode, next: Function)=> { const hook:Function = hookUtil.bindHookContext(scanNode, hookUtil.sequenceHooks(hooks)); - const hookTarget:object = scanNode.context!.inputs.get(ScanInputKeys.LifeCyclePhase)[lifeCyclePhase]; + const hookTarget:object = scanNode.context.lifeCyclePhaseNodes[lifeCyclePhase]; HookMetadata.defineMetadata(hookTarget, hook); next(); } diff --git a/src/decorators/Module.decorator.ts b/src/decorators/Module.decorator.ts index edc7d9c..3b378ce 100644 --- a/src/decorators/Module.decorator.ts +++ b/src/decorators/Module.decorator.ts @@ -1,112 +1,23 @@ -import { IScanNode, Metadata, HookMetadata, hookUtil } from '@augejs/provider-scanner'; - +import { Metadata } from '@augejs/provider-scanner'; import { Parent } from './Parent.decorator'; import { Name } from './Name.decorator'; -import { ScanHook } from './ScanHook.decorator'; import { Injectable } from '../ioc'; -import { ScanInputKeys, ScanOutputKeys } from '../constants'; -import { Container } from '../ioc'; -import { objectPath, getConfigAccessPath } from '../utils'; -import { Config } from './Config.decorator'; -import { ConfigLoader } from './ConfigLoader.decorator'; export interface IModuleOptions { name?:string, providers?:any[], subModules?: any[], - hooks?: Function | Function[], } export function Module(options?:IModuleOptions):ClassDecorator { return function(target: Function) { - const optionsHooks: Function [] = hookUtil.ensureHooks(options?.hooks || null) - const hooks: Function [] = [ - runtimeConfigHook, - dependencyInjectionHook, - ...optionsHooks, - ]; - - const optionsProvides: any[] = Array.isArray(options?.providers) ? options!.providers : []; - const optionsSubModules: Function[] = Array.isArray(options?.subModules) ? options!.subModules : []; - - const children: object[] = [ - ...optionsProvides, - ...optionsSubModules, - ]; - - optionsProvides.forEach((provider: object) => { - HookMetadata.defineMetadata(provider, [ - runtimeConfigHook, - dependencyInjectionHook, - ]); - }) - Metadata.decorate([ - Injectable(), Name(options?.name), - ScanHook(hooks), - Parent(children), + Parent([ + ...Array.isArray(options?.providers) ? options!.providers : [], + ...Array.isArray(options?.subModules) ? options!.subModules : [], + ]), + Injectable(), ] ,target); } } - -async function runtimeConfigHook(scanNode: IScanNode, next: Function) { - const configAccessPath:string = getConfigAccessPath(scanNode.namePaths); - const runtimeConfig:object = scanNode.context!.outputs.get(ScanOutputKeys.RuntimeConfig); - // provide config. - // https://www.npmjs.com/package/object-path - let providerConfig:object = Config.getMetadata(scanNode.provider); - const providerConfigLoader:Function = ConfigLoader.getMetadata(scanNode.provider); - const providerConfigLoaderConfigResult:any = await providerConfigLoader(providerConfig, scanNode); - if (providerConfigLoaderConfigResult !== undefined) { - providerConfig = providerConfigLoaderConfigResult; - } - - // https://www.npmjs.com/package/extend - objectPath.set(runtimeConfig, configAccessPath, providerConfig); - - await next(); -} - -async function dependencyInjectionHook(scanNode: IScanNode, next: Function) { - const container:Container = scanNode.context!.inputs.get(ScanInputKeys.Container); - let instanceFactory:Function | null = null; - - const provider:any = scanNode.provider; - // here we need deal with kinds of provider value. - if (typeof provider === 'function') { - container.bind(provider).toSelf(); - instanceFactory = () => { - return container.get(provider); - } - } else if (typeof provider === 'object') { - if (Object.prototype.hasOwnProperty.call(provider, 'id') && !!provider.id) { - const identifier:any = provider.id; - if (Object.prototype.hasOwnProperty.call(provider, 'useValue')) { - container.bind(identifier).toConstantValue(provider.useValue); - instanceFactory = () => { - return container.get(identifier); - } - } else if (Object.prototype.hasOwnProperty.call(provider, 'useClass')) { - container.bind(identifier).to(provider.useClass); - instanceFactory = () => { - return container.get(identifier); - } - } else if (Object.prototype.hasOwnProperty.call(provider, 'useFactory')) { - container.bind(identifier).toDynamicValue(()=>{ - const scanNodeConfig:any = scanNode.outputs.get(ScanOutputKeys.InstanceConfig); - return provider.useFactory(scanNodeConfig, scanNode); - }); - instanceFactory = () => { - return container.get(identifier); - } - } - } - } - - if (instanceFactory !== null) { - scanNode.outputs.set(ScanOutputKeys.InstanceFactory, instanceFactory); - } - - await next(); -} diff --git a/src/decorators/Name.decorator.test.ts b/src/decorators/Name.decorator.test.ts index 58def72..d0a3994 100644 --- a/src/decorators/Name.decorator.test.ts +++ b/src/decorators/Name.decorator.test.ts @@ -1,14 +1,13 @@ import { Name } from './Name.decorator'; -import { NameMetadata } from '@augejs/provider-scanner'; describe('decorators: Name.decorator.test', () => { it('Name.decorator should have correct metadata', () => { @Name() class A {} - expect(NameMetadata.getMetadata(A)).toBe('a'); + expect(Name.getMetadata(A)).toBe('a'); @Name('test') class B {} - expect(NameMetadata.getMetadata(B)).toBe('test'); + expect(Name.getMetadata(B)).toBe('test'); }) }); diff --git a/src/decorators/Name.decorator.ts b/src/decorators/Name.decorator.ts index ba02d6a..3405434 100644 --- a/src/decorators/Name.decorator.ts +++ b/src/decorators/Name.decorator.ts @@ -1,6 +1,16 @@ import { NameMetadata } from '@augejs/provider-scanner'; + export function Name(name?: string): ClassDecorator { return function(target: Function) { - NameMetadata.defineMetadata(target, name); + Name.defineMetadata(target, name); } } + +Name.defineMetadata = (target: object, name?: string) => { + NameMetadata.defineMetadata(target, name); +} + +Name.getMetadata = (target: object): string => { + return NameMetadata.getMetadata(target); +} + diff --git a/src/decorators/Parent.decorator.test.ts b/src/decorators/Parent.decorator.test.ts index cfddfac..6c1e347 100644 --- a/src/decorators/Parent.decorator.test.ts +++ b/src/decorators/Parent.decorator.test.ts @@ -1,5 +1,5 @@ import { Parent } from './Parent.decorator'; -import { ParentMetadata } from '@augejs/provider-scanner'; + describe('decorators: Parent.decorator.test', () => { it('Parent.decorator should have correct metadata', () => { @@ -12,7 +12,7 @@ describe('decorators: Parent.decorator.test', () => { } ]) class A {} - expect(ParentMetadata.getMetadata(A)).toEqual([ + expect(Parent.getMetadata(A)).toEqual([ { a: 1 }, diff --git a/src/decorators/Parent.decorator.ts b/src/decorators/Parent.decorator.ts index 4e97c4b..44a911b 100644 --- a/src/decorators/Parent.decorator.ts +++ b/src/decorators/Parent.decorator.ts @@ -1,7 +1,21 @@ import { ParentMetadata } from '@augejs/provider-scanner'; export function Parent(children: object[]):ClassDecorator { - return function(target: Function) { - ParentMetadata.defineMetadata(target, children); + return (target: Function) => { + Parent.defineMetadata(target, children); } } + +Parent.defineMetadata = (target: object, children: any[]) => { + ParentMetadata.defineMetadata(target, children); +} + +Parent.getMetadata = (target: object): object[] => { + return ParentMetadata.getMetadata(target); +} + + + + + + diff --git a/src/decorators/ScanHook.decorator.test.ts b/src/decorators/ScanHook.decorator.test.ts index 8355660..af7b7cb 100644 --- a/src/decorators/ScanHook.decorator.test.ts +++ b/src/decorators/ScanHook.decorator.test.ts @@ -1,11 +1,11 @@ import { ScanHook } from './ScanHook.decorator'; -import { HookMetadata } from '@augejs/provider-scanner'; + describe('decorators: ScanHook.decorator', () => { it('ScanHook.decorator should have correct metadata', () => { const hook: Function = ()=>{}; @ScanHook(hook) class A {}; - expect(HookMetadata.getMetadata(A)[0]).toStrictEqual(hook); + expect(ScanHook.getMetadata(A)[0]).toStrictEqual(hook); }) }); diff --git a/src/decorators/ScanHook.decorator.ts b/src/decorators/ScanHook.decorator.ts index 7e18eb3..c6e1b7a 100644 --- a/src/decorators/ScanHook.decorator.ts +++ b/src/decorators/ScanHook.decorator.ts @@ -1,6 +1,15 @@ import { HookMetadata } from '@augejs/provider-scanner'; export function ScanHook (hooks: Function | Function[]):ClassDecorator { return function(target: Function) { - HookMetadata.defineMetadata(target, hooks); + ScanHook.defineMetadata(target, hooks); } } + +ScanHook.defineMetadata = (target: object, hooks: Function | Function[]) => { + HookMetadata.defineMetadata(target, hooks); +} + +ScanHook.getMetadata = (target: object): Function[] => { + return HookMetadata.getMetadata(target); +} + diff --git a/src/decorators/ScanPriority.decorator.ts b/src/decorators/ScanPriority.decorator.ts new file mode 100644 index 0000000..aed16aa --- /dev/null +++ b/src/decorators/ScanPriority.decorator.ts @@ -0,0 +1,16 @@ +import { ScanPriorityMetadata } from '@augejs/provider-scanner'; + +export function ScanPriority(priority?: number): ClassDecorator { + return function(target: Function) { + ScanPriority.defineMetadata(target, priority); + } +} + +ScanPriority.defineMetadata = (target: object, priority?: number) => { + ScanPriorityMetadata.defineMetadata(target, priority); +} + +ScanPriority.getMetadata = (target: object): number => { + return ScanPriorityMetadata.getMetadata(target); +} + diff --git a/src/decorators/Value.decorator.ts b/src/decorators/Value.decorator.ts index 9bf2ec5..e5612b3 100644 --- a/src/decorators/Value.decorator.ts +++ b/src/decorators/Value.decorator.ts @@ -1,6 +1,4 @@ -import { IScanNode } from "@augejs/provider-scanner"; -import { ScanOutputKeys } from "../constants"; -import { objectPath, getConfigAccessPath } from '../utils'; +import { IScanNode } from '../utils'; const noopObject = {}; @@ -8,30 +6,20 @@ const noopObject = {}; * @param {string} [path=''] * @returns {PropertyDecorator} */ -export function Value(path:string = '.'):PropertyDecorator { +export function Value(path?:string):PropertyDecorator { return (target: Object, propertyKey: string | symbol) => { let memoizedName = `$memoized_${propertyKey.toString()}`; (target as any)[memoizedName] = noopObject; - const descriptor:PropertyDescriptor = { - get():object { - if ((this as any)[memoizedName] === noopObject) { + get():any { + const instance:any = this as any; + if (instance[memoizedName] === noopObject) { // expensive calculate - const scanNode:IScanNode = (this as any).$scanNode; - const runtimeConfig:object = scanNode.context!.outputs.get(ScanOutputKeys.RuntimeConfig); - const configAccessPath:string = getConfigAccessPath(scanNode.namePaths, path); - let result:any = runtimeConfig; - if (configAccessPath) { - // https://www.npmjs.com/package/object-path - if (!objectPath.has(runtimeConfig, configAccessPath)) { - throw new Error(`can't find any config from the path ${path}`); - } else { - result = objectPath.get(runtimeConfig, configAccessPath); - } - } - (this as any)[memoizedName] = result; + const scanNode:IScanNode = instance.$scanNode; + const result:any = scanNode.getConfig(path) + instance[memoizedName] = result; } - return (this as any)[memoizedName] as object; + return instance[memoizedName]; }, }; diff --git a/src/decorators/index.ts b/src/decorators/index.ts index 1252e5a..5401af1 100644 --- a/src/decorators/index.ts +++ b/src/decorators/index.ts @@ -1,4 +1,3 @@ -export * from './Application.decorator'; export * from './Config.decorator'; export * from './ConfigLoader.decorator'; export * from './Value.decorator'; @@ -8,3 +7,6 @@ export * from './Parent.decorator'; export * from './ScanHook.decorator'; export * from './GetLogger.decorator'; export * from './LifeCyclePhaseHook.decorator'; +export * from './ScanPriority.decorator'; +export * from './ChildrenHooksCompositeFunction.decorator'; + diff --git a/src/ioc/ioc.ts b/src/ioc/ioc.ts index 1549c98..cf3cecf 100644 --- a/src/ioc/ioc.ts +++ b/src/ioc/ioc.ts @@ -1,5 +1,4 @@ import { injectable, inject, Container, BindingScopeEnum } from 'inversify'; - export { injectable as Injectable, inject as Inject, diff --git a/src/logger/ConsoleLogTransport.ts b/src/logger/ConsoleLogTransport.ts index c9101b5..b5079ef 100644 --- a/src/logger/ConsoleLogTransport.ts +++ b/src/logger/ConsoleLogTransport.ts @@ -1,7 +1,19 @@ import { ILogItem, ILogTransport } from "./Logger.interface"; +import { LogLevel } from "./LogLevel"; export class ConsoleLogTransport implements ILogTransport { printMessage(logItem:ILogItem) { - console.log(`[${logItem.level}] - ${logItem.context} ${logItem.message}`); + const methodNameMap = { + [LogLevel.VERBOSE]: 'debug', + [LogLevel.DEBUG]: 'debug', + [LogLevel.INFO]: 'info', + [LogLevel.WARN]: 'warn', + [LogLevel.ERROR]: 'error', + }; + + const methodName:string = methodNameMap[logItem.level]; + if (!methodName) return; + + (console as any)[methodName](`${new Date(logItem.timestamp).toISOString()} [${logItem.level}] - ${logItem.context} ${logItem.message}`); } } diff --git a/src/logger/Logger.interface.ts b/src/logger/Logger.interface.ts index 17b99d6..a5e636f 100644 --- a/src/logger/Logger.interface.ts +++ b/src/logger/Logger.interface.ts @@ -10,6 +10,7 @@ export interface ILogItem { context: string message: any level: string + timestamp: number } export interface ILogTransport { diff --git a/src/logger/Logger.ts b/src/logger/Logger.ts index 2399169..b296675 100644 --- a/src/logger/Logger.ts +++ b/src/logger/Logger.ts @@ -56,6 +56,7 @@ export class Logger implements ILogger { public static addTransport(transport: ILogTransport) { if (logTransports.includes(transport)) return; logTransports.push(transport); + processQueueLogItems(); } @@ -69,6 +70,10 @@ export class Logger implements ILogger { logTransports.length = 0; } + public static getTransportCount(): number { + return logTransports.length; + } + public static clear() { queueLogItems.length = 0; } @@ -80,6 +85,7 @@ export class Logger implements ILogger { context: this.context, message, level: LogLevel.ERROR, + timestamp: Date.now(), }); } @@ -88,6 +94,7 @@ export class Logger implements ILogger { context: this.context, message, level: LogLevel.VERBOSE, + timestamp: Date.now(), }); } @@ -96,6 +103,7 @@ export class Logger implements ILogger { context: this.context, message, level: LogLevel.DEBUG, + timestamp: Date.now(), }); } @@ -104,6 +112,7 @@ export class Logger implements ILogger { context: this.context, message, level: LogLevel.INFO, + timestamp: Date.now(), }); } @@ -112,6 +121,7 @@ export class Logger implements ILogger { context: this.context, message, level: LogLevel.WARN, + timestamp: Date.now(), }); } }; diff --git a/src/main.example.ts b/src/main.example.ts index 6fce0df..b708727 100644 --- a/src/main.example.ts +++ b/src/main.example.ts @@ -1,41 +1,56 @@ -import { Application, Module, Config, Value , boot } from './main'; +import { Module, Config, Value, boot, ILogger, GetLogger } from './main'; @Module() @Config({ - fullName: "1231313", + fullName: "augejs awesome~", hello: { - name: 1, - age: 'asdadad', - adadad: 'adadadad' + name: 'augejs', + age: 12, } }) class Module1 { - - @Value('./hello') + @Value('hello') testName!:string; - async onInit() { - console.log('Module1 onInit'); + @Value('fullName') + fullName!:string; + + @Value('hello.age') + age!:number; + + @GetLogger() + logger!:ILogger; - console.log('--->', this.testName); + async onInit() { + this.logger.info('Module1 onInit'); + this.logger.info(`config: hello: ${JSON.stringify(this.testName)}`); + this.logger.info(`config fullName: ${this.fullName}`); + this.logger.info(`config age: ${this.age}`); } } @Module() class Module2 { + @GetLogger() + logger!:ILogger; + async onInit() { - console.log('Module2 onInit'); + this.logger.info('Module2 onInit'); } } -@Application({ +@Module({ subModules: [ Module1, [Module2] ] }) class AppModule { + + @GetLogger() + logger!:ILogger; + async onInit() { - console.log('AppModule onInit'); + this.logger.info('AppModule onInit'); } } diff --git a/src/main.ts b/src/main.ts index 0b138e6..bf033ea 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,5 +2,10 @@ export * from './decorators'; export * from './ioc'; export * from './logger'; export * from './utils'; -export * from './constants'; -export * from '@augejs/provider-scanner'; + +export { + hookUtil, + Metadata, + createScanNodeScanHook, +} from '@augejs/provider-scanner'; + diff --git a/src/utils/boot.ts b/src/utils/boot.ts index 865a766..83477cb 100644 --- a/src/utils/boot.ts +++ b/src/utils/boot.ts @@ -1,3 +1,227 @@ -import { scan } from '@augejs/provider-scanner'; +import { argv } from 'yargs'; +import { scan, IScanNode, IScanContext, hookUtil, HookMetadata, createScanNodeScanHook } from '@augejs/provider-scanner'; +import { getConfigAccessPath } from './config.util'; +import { objectPath, objectExtend } from './object.util'; +import { BindingScopeEnum, Container } from '../ioc'; +import { Config, ConfigLoader } from '../decorators'; +import { ILogger, Logger, ConsoleLogTransport } from '../logger'; + +const DefaultLifeCyclePhases = +{ + startupLifecyclePhase: [ + 'onInit', 'onAppWillReady', '__onAppReady__' + ], + + readyLifecyclePhase: [ + 'onAppDidReady', + ], + + shutdownLifecyclePhase: [ + 'onAppWillClose', + ] +}; + +interface IBootOptions { + containerOptions?: { [key: string]: any} + lifeCyclePhases?: { [key: string]: string[]} +} + +const logger:ILogger = Logger.getLogger('Boot'); + +export const boot = async (appModule:Function, options?:IBootOptions): Promise => { + const containerOptions = { + defaultScope: BindingScopeEnum.Singleton, + autoBindInjectable: false, + skipBaseClassChecks: true, + ...(options?.containerOptions || {}) + }; + + const lifeCyclePhases = options?.lifeCyclePhases || DefaultLifeCyclePhases; + + return await scan(appModule, { + // context level hooks. + contextScanHook: hookUtil.nestHooks([ + // setup the boot env + async function setupEnvHook(context: IScanContext, next: Function) { + context.container = new Container(containerOptions); + context.processArgv = argv; + context.globalConfig = {}; + objectExtend(true, context.globalConfig, argv); + context.lifeCyclePhaseNodes = {}; + Object.keys(lifeCyclePhases).forEach((lifeCyclePhaseName: string) => { + context.lifeCyclePhaseNodes[lifeCyclePhaseName] = {}; + }); + await next(); + }, + + async function setupConfig(context: IScanContext, next: Function) { + await createScanNodeScanHook(context.rootScanNode!, async (scanNode: IScanNode, next: Function)=> { + await next(); + const configAccessPath:string = getConfigAccessPath(scanNode.namePaths); + const globalConfig:object = scanNode.context.globalConfig; + // helper to get config + scanNode.getConfig = (path?:string): any => { + const configAccessPath:string = getConfigAccessPath(scanNode.namePaths, path); + return objectPath.get(globalConfig, configAccessPath); + } + // provide config. + // https://www.npmjs.com/package/object-path + let providerConfig:object = Config.getMetadata(scanNode.provider); + const providerConfigLoader:Function = ConfigLoader.getMetadata(scanNode.provider); + const providerConfigLoaderConfigResult:any = await providerConfigLoader(scanNode); + if (providerConfigLoaderConfigResult !== undefined) { + objectExtend(true, providerConfig, providerConfigLoaderConfigResult); + } + // current override previous + let preProviderConfig:any = objectPath.get(globalConfig, configAccessPath); + if (preProviderConfig) { + objectExtend(true, preProviderConfig, providerConfig); + } else { + preProviderConfig = providerConfig; + } + // https://www.npmjs.com/package/extend + objectPath.set(globalConfig, configAccessPath, preProviderConfig); + })(context); + // the argv has highest priority + objectExtend(true, context.globalConfig, argv); + await next(); + }, + + async function setupLfeCyclePhasesHook(context: IScanContext, next: Function) { + await next(); + const rootScanNode:IScanNode = context.rootScanNode!; + const lifeCyclePhasesHookMap:{ [key: string]: Function} = {}; + + Object.keys(lifeCyclePhases).forEach((lifeCyclePhaseName: string) => { + const lifeCycleNames:string[] = lifeCyclePhases[lifeCyclePhaseName]; + const childrenHook:Function = hookUtil.sequenceHooks(lifeCycleNames.map((lifeCycleName:string) => { + return buildScanNodeInstanceLifeCycleHook(rootScanNode, lifeCycleName); + })); + + const selfHook:Function = hookUtil.parallelHooks(HookMetadata.getMetadata(context.lifeCyclePhaseNodes[lifeCyclePhaseName])); + lifeCyclePhasesHookMap[lifeCyclePhaseName] = hookUtil.sequenceHooks([ + childrenHook, + selfHook + ]); + }); + + // startup + try { + await lifeCyclePhasesHookMap.startupLifecyclePhase(rootScanNode); + } catch(err) { + logger.error(`startupLifecyclePhase Error \n ${err} \n ${err?.stack}`); + } + + process.nextTick(() => { + (async () => { + try { + await lifeCyclePhasesHookMap.readyLifecyclePhase(rootScanNode); + + // if here there is no LogTransport, add ConsoleLogTransport as default. + if (Logger.getTransportCount() === 0) { + Logger.addTransport(new ConsoleLogTransport()); + } + } catch(err) { + logger.error(`readyLifecyclePhase Error \n ${err} \n ${err?.stack}`); + } + })(); + }) + + //shutdown + // https://hackernoon.com/graceful-shutdown-in-nodejs-2f8f59d1c357 + // https://blog.risingstack.com/graceful-shutdown-node-js-kubernetes/ + process.on('exit', () => { + (async () => { + try { + await lifeCyclePhasesHookMap.shutdownLifecyclePhase(rootScanNode); + } catch(err:any) { + logger.error('ShutDown Error \n' + err); + } + })() + }) + } + ]), + + // scanNode level hooks. + scanNodeScanHook: hookUtil.nestHooks([ + async function setupScanNodeDIHook(scanNode: IScanNode, next: Function) { + const container:Container = scanNode.context.container; + + let instanceFactory:Function | null = null; + const provider:any = scanNode.provider; + // here we need deal with kinds of provider value. + if (typeof provider === 'function') { + container.bind(provider).toSelf(); + instanceFactory = () => { + return container.get(provider); + } + } else if (typeof provider === 'object') { + // https://github.com/inversify/InversifyJS#the-inversifyjs-features-and-api + if (Object.prototype.hasOwnProperty.call(provider, 'id') && !!provider.id) { + const identifier:any = provider.id; + if (Object.prototype.hasOwnProperty.call(provider, 'useValue')) { + container.bind(identifier).toConstantValue(provider.useValue); + instanceFactory = () => { + return container.get(identifier); + } + } else if (Object.prototype.hasOwnProperty.call(provider, 'useClass')) { + container.bind(identifier).to(provider.useClass); + instanceFactory = () => { + return container.get(identifier); + } + } else if (Object.prototype.hasOwnProperty.call(provider, 'useFactory')) { + container.bind(identifier).toDynamicValue(()=>{ + return provider.useFactory(scanNode.parent); + }); + instanceFactory = () => { + return container.get(identifier); + } + } else if (Object.prototype.hasOwnProperty.call(provider, 'useExisting')) { + container.bind(identifier).toDynamicValue(()=>{ + return container.get(identifier); + }); + } + } + } + + await next(); + + let instance:any = null; + if (instanceFactory) { + instance = await instanceFactory(); + } + scanNode.instance = instance; + // add self life cycle + if (instance) { + // keep the reference to scanNode + instance.$scanNode = scanNode; + } + } + ]), + }); +}; + +function buildScanNodeInstanceLifeCycleHook(scanNode: IScanNode, lifecycleName: string):Function { + let selfLifeCycleHook:Function = hookUtil.noopHook; + let instance:any = scanNode.instance + if (instance) { + // bind the life cycle + if(typeof instance[lifecycleName] === 'function') { + selfLifeCycleHook = (instance[lifecycleName] as Function).bind(instance); + } + } + + // build the children life cycle + const childrenLifeCycleHook:Function = hookUtil.parallelHooks( + scanNode.children.map((child:IScanNode) => { + return buildScanNodeInstanceLifeCycleHook(child, lifecycleName); + }) + ) + + // children life cycle first + return hookUtil.bindHookContext(scanNode, hookUtil.sequenceHooks([ + childrenLifeCycleHook, + selfLifeCycleHook, + ])); +} -export const boot = scan; diff --git a/src/utils/index.ts b/src/utils/index.ts index 1623a6b..a8094a3 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,6 @@ export * from './object.util'; export * from './config.util'; +export * from './provider-scanner'; export * from './boot'; +export * from './provider-scanner'; + diff --git a/src/utils/provider-scanner.ts b/src/utils/provider-scanner.ts new file mode 100644 index 0000000..e9afa93 --- /dev/null +++ b/src/utils/provider-scanner.ts @@ -0,0 +1,18 @@ +import { Container } from '../ioc'; +import { IScanContext as IProviderScanContext, IScanNode as IProviderScanNode } from '@augejs/provider-scanner'; + +export interface IScanContext extends IProviderScanContext { + rootScanNode?: IScanNode, + container: Container + processArgv: { [key: string]: any } + lifeCyclePhaseNodes: { [key: string]: object } + globalConfig: { [key: string]: any } +} + +export interface IScanNode extends IProviderScanNode { + context: IScanContext; + children: IScanNode[]; + parent: IScanNode | null; + instance: object | null + getConfig(path?: string): any +} diff --git a/tsconfig.json b/tsconfig.json index 359bda2..7a60cc4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -66,5 +66,5 @@ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": ["src/**/*.ts"], - "exclude": ["node_modules", "**/*.test.ts", "**/*.local.ts"] + "exclude": ["node_modules", "**/*.test.ts", "**/*.local.ts", "**/*.example.ts"] }