{"id":13487393,"url":"https://github.com/eggjs/tegg","last_synced_at":"2026-06-08T19:00:38.410Z","repository":{"id":37430849,"uuid":"126216630","full_name":"eggjs/tegg","owner":"eggjs","description":"Strong Type framework with eggjs.","archived":false,"fork":false,"pushed_at":"2026-06-08T16:31:44.000Z","size":10821,"stargazers_count":265,"open_issues_count":35,"forks_count":45,"subscribers_count":18,"default_branch":"master","last_synced_at":"2026-06-08T17:16:02.060Z","etag":null,"topics":["aop","egg","ioc","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eggjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"open_collective":"eggjs"}},"created_at":"2018-03-21T17:29:50.000Z","updated_at":"2026-06-08T03:32:41.000Z","dependencies_parsed_at":"2022-07-08T18:44:07.503Z","dependency_job_id":"6b125c94-56bc-4d87-8087-22aa7aec6cb4","html_url":"https://github.com/eggjs/tegg","commit_stats":{"total_commits":364,"total_committers":28,"mean_commits":13.0,"dds":0.4148351648351648,"last_synced_commit":"f01fb639b153a907fd9c951d4b1e40ba101b43d0"},"previous_names":[],"tags_count":471,"template":false,"template_full_name":null,"purl":"pkg:github/eggjs/tegg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Ftegg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Ftegg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Ftegg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Ftegg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eggjs","download_url":"https://codeload.github.com/eggjs/tegg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eggjs%2Ftegg/sbom","scorecard":{"id":368802,"data":{"date":"2025-08-11","repo":{"name":"github.com/eggjs/tegg","commit":"cfeabf2ca5f1350df9a8d1c7c4baa99ad776483c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":6.1,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql-analysis.yml:26","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql-analysis.yml:27","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: no topLevel permission defined: .github/workflows/nodejs.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":10,"reason":"27 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":4,"reason":"Found 12/30 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":2,"reason":"dependency not pinned by hash detected -- score normalized to 2","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:70: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/nodejs.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/nodejs.yml:81: update your workflow using https://app.stepsecurity.io/secureworkflow/eggjs/tegg/nodejs.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:35","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:38","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:73","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:76","Info:   0 out of   8 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   2 out of   6 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: SAST configuration detected: CodeQL","Info: all commits (12) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T12:30:05.466Z","repository_id":37430849,"created_at":"2025-08-18T12:30:05.466Z","updated_at":"2025-08-18T12:30:05.466Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34075970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aop","egg","ioc","typescript"],"created_at":"2024-07-31T18:00:58.741Z","updated_at":"2026-06-08T19:00:38.364Z","avatar_url":"https://github.com/eggjs.png","language":"TypeScript","funding_links":["https://opencollective.com/eggjs"],"categories":["TypeScript","typescript","仓库"],"sub_categories":["框架"],"readme":"# `@eggjs/tegg`\n\n## Install\n\n```shell\n# tegg 注解\nnpm i --save @eggjs/tegg\n# tegg 插件\nnpm i --save @eggjs/tegg-plugin\n```\n\n## Config\n\n```js\n// config/plugin.js\nexports.tegg = {\n  package: '@eggjs/tegg-plugin',\n  enable: true,\n};\n```\n\n```js\n// config/config.default.js\n{\n  tegg: {\n    // 读取模块支持自定义配置，可用于扩展或过滤不需要的模块文件\n    readModuleOptions: {\n      extraFilePattern: ['!**/dist', '!**/release'],\n    },\n  };\n}\n```\n\n## Usage\n\n### 原型\n\nmodule 中的对象基本信息，提供了\n\n- 实例化方式：每个请求实例化/全局单例/每次注入都实例化\n- 访问级别：module 外是否可访问\n\n#### ContextProto\n\n每次请求都会实例化一个 ContextProto，并且只会实例化一次\n\n##### 定义\n\n```typescript\n@ContextProto(params: {\n  // 原型的实例化名称\n  // 默认行为：会把 Proto 的首字母转为小写\n  // 如 UserAdapter 会转换为 userAdapter\n  // 如果有不符合预期的可以手动指定,比如\n  // @ContextProto({ name: 'mistAdapter' })\n  // MISTAdapter\n  // MISTAdapter 的实例名称即为 mistAdapter\n  name?: string;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // PRIVATE: 仅 module 内可访问\n  // PUBLIC: 全局可访问\n  // 默认值为 PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n##### 示例\n\n###### 简单示例\n\n```typescript\nimport { ContextProto } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  async hello(): Promise\u003cstring\u003e {\n    return 'hello, module!';\n  }\n}\n\n```\n\n###### 复杂示例\n\n```typescript\nimport { ContextProto, AccessLevel } from '@eggjs/tegg';\n\n@ContextProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'helloInterface',\n})\nexport default class HelloService {\n  async hello(user: User): Promise\u003cstring\u003e {\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n#### SingletonProto\n\n整个应用声明周期只会实例化一个 SingletonProto\n\n##### 定义\n\n```typescript\n@SingletonProto(params: {\n  // 原型的实例化名称\n  // 默认行为：会把 Proto 的首字母转为小写\n  // 如 UserAdapter 会转换为 userAdapter\n  // 如果有不符合预期的可以手动指定,比如\n  // @SingletonProto({ name: 'mistAdapter' })\n  // MISTAdapter\n  // MISTAdapter 的实例名称即为 mistAdapter\n  name?: string;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // PRIVATE: 仅 module 内可访问\n  // PUBLIC: 全局可访问\n  // 默认值为 PRIVATE\n  accessLevel?: AccessLevel;\n})\n```\n\n##### 示例\n\n###### 简单示例\n\n```typescript\nimport { SingletonProto } from '@eggjs/tegg';\n\n@SingletonProto()\nexport class HelloService {\n  async hello(): Promise\u003cstring\u003e {\n    return 'hello, module!';\n  }\n}\n\n```\n\n###### 复杂示例\n\n```typescript\nimport { SingletonProto, AccessLevel } from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'helloInterface',\n})\nexport class HelloService {\n  async hello(user: User): Promise\u003cstring\u003e {\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n#### MultiInstanceProto\n\n支持一个类有多个实例。比如说 logger 可能会初始化多个，用来输出到不同文件，db 可能会初始化多个，用来连接不同的数据库。\n使用这个注解可以方便的对接外部资源。\n\n##### 定义\n```ts\n// 静态定义\n@MultiInstanceProto(params: {\n  // 对象的生命周期\n  // CONTEXT: 每个上下文都有一个实例\n  // SINGLETON: 整个应用生命周期只有一个实例\n  initType?: ObjectInitTypeLike;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // PRIVATE: 仅 module 内可访问\n  // PUBLIC: 全局可访问\n  // 默认值为 PRIVATE\n  accessLevel?: AccessLevel;\n\n  // 高阶参数，指定类型的实现原型\n  protoImplType?: string;\n\n  // 对象元信息\n  objects: ObjectInfo[];\n})\n\n// 动态定义\n@MultiInstanceProto(params: {\n  // 对象的生命周期\n  // CONTEXT: 每个上下文都有一个实例\n  // SINGLETON: 整个应用生命周期只有一个实例\n  initType?: ObjectInitTypeLike;\n\n  // 对象是在 module 内可访问还是全局可访问\n  // PRIVATE: 仅 module 内可访问\n  // PUBLIC: 全局可访问\n  // 默认值为 PRIVATE\n  accessLevel?: AccessLevel;\n\n  // 高阶参数，指定类型的实现原型\n  protoImplType?: string;\n\n  // 动态调用，获取对象元信息\n  // 仅会调用一次，调用后结果会被缓存\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext): ObjectInfo[];\n})\n```\n\n##### 示例\n\n首先定义一个自定义 Qualifier 注解\n```ts\nimport {\n  QualifierUtil,\n  EggProtoImplClass,\n} from '@eggjs/tegg';\n\nexport const LOG_PATH_ATTRIBUTE = Symbol.for('LOG_PATH_ATTRIBUTE');\n\nexport function LogPath(name: string) {\n  return function(target: any, propertyKey: PropertyKey) {\n    QualifierUtil.addProperQualifier(target.constructor as EggProtoImplClass, propertyKey, LOG_PATH_ATTRIBUTE, name);\n  };\n}\n\n```\n\n定一个具体实现\n\n```typescript\nimport {\n  MultiInstanceProto,\n  MultiInstancePrototypeGetObjectsContext,\n  LifecycleInit,\n  LifecycleDestroy,\n  QualifierUtil,\n  EggProtoImplClass,\n} from '@eggjs/tegg';\nimport { EggObject, ModuleConfigUtil, EggObjectLifeCycleContext } from '@eggjs/tegg/helper';\nimport fs from 'node:fs';\nimport { Writable } from 'node:stream';\nimport path from 'node:path';\nimport { EOL } from 'node:os';\n\nexport const LOG_PATH_ATTRIBUTE = Symbol.for('LOG_PATH_ATTRIBUTE');\n\n@MultiInstanceProto({\n  // 从 module.yml 中动态获取配置来决定需要初始化几个对象\n  getObjects(ctx: MultiInstancePrototypeGetObjectsContext) {\n    const config = ModuleConfigUtil.loadModuleConfigSync(ctx.unitPath);\n    return (config as any).features.logger.map(name =\u003e {\n      return {\n        name: 'dynamicLogger',\n        qualifiers: [{\n          attribute: LOG_PATH_ATTRIBUTE,\n          value: name,\n        }],\n      }\n    });\n  },\n})\nexport class DynamicLogger {\n  stream: Writable;\n  loggerName: string;\n\n  @LifecycleInit()\n  async init(ctx: EggObjectLifeCycleContext, obj: EggObject) {\n    // 获取需要实例化对象的 Qualifieri\n    const loggerName = obj.proto.getQualifier(LOG_PATH_ATTRIBUTE);\n    this.loggerName = loggerName as string;\n    this.stream = fs.createWriteStream(path.join(ctx.loadUnit.unitPath, `${loggerName}.log`));\n  }\n\n  @LifecycleDestroy()\n  async destroy() {\n    return new Promise\u003cvoid\u003e((resolve, reject) =\u003e {\n      this.stream.end(err =\u003e {\n        if (err)  {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n\n  info(msg: string) {\n    return new Promise\u003cvoid\u003e((resolve, reject) =\u003e {\n      this.stream.write(msg + EOL,err =\u003e {\n        if (err)  {\n          return reject(err);\n        }\n        return resolve();\n      });\n    });\n  }\n\n}\n```\n\n使用 DynamicLogger.\n```ts\n@SingletonProto()\nexport class Foo {\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  // 通过自定义注解来指定 Qualifier\n  @LogPath('foo')\n  fooDynamicLogger: DynamicLogger;\n\n  @Inject({\n    name: 'dynamicLogger',\n  })\n  @LogPath('bar')\n  barDynamicLogger: DynamicLogger;\n\n  async hello(): Promise\u003cvoid\u003e {\n    await this.fooDynamicLogger.info('hello, foo');\n    await this.barDynamicLogger.info('hello, bar');\n  }\n}\n```\n\n\n\n#### 生命周期 hook\n\n由于对象的生命周期交给了容器来托管，代码中无法感知什么时候对象初始化，什么时候依赖被注入了。所以提供了生命周期 hook 来实现这些通知的功能。\n\n##### 定义\n\n```typescript\n/**\n * lifecycle hook interface for egg object\n */\ninterface EggObjectLifecycle {\n  /**\n   * call after construct\n   */\n  postConstruct?(): Promise\u003cvoid\u003e;\n\n  /**\n   * call before inject deps\n   */\n  preInject?(): Promise\u003cvoid\u003e;\n\n  /**\n   * call after inject deps\n   */\n  postInject?(): Promise\u003cvoid\u003e;\n\n  /**\n   * before object is ready\n   */\n  init?(): Promise\u003cvoid\u003e;\n\n  /**\n   * call before destroy\n   */\n  preDestroy?(): Promise\u003cvoid\u003e;\n\n  /**\n   * destroy the object\n   */\n  destroy?(): Promise\u003cvoid\u003e;\n}\n```\n\n##### 示例\n\n```typescript\nimport { EggObjectLifecycle } from '@eggjs/tegg';\n\n@ContextProto()\nexport class Foo implements EggObjectLifecycle {\n  // 构造函数\n  constructor() {\n  }\n\n  async postConstruct(): Promise\u003cvoid\u003e {\n    console.log('对象构造完成');\n  }\n\n  async preInject(): Promise\u003cvoid\u003e {\n    console.log('依赖将要注入');\n  }\n\n  async postInject(): Promise\u003cvoid\u003e {\n    console.log('依赖注入完成');\n  }\n\n  async init(): Promise\u003cvoid\u003e {\n    console.log('执行一些异步的初始化过程');\n  }\n\n  async preDestroy(): Promise\u003cvoid\u003e {\n    console.log('对象将要释放了');\n  }\n\n  async destroy(): Promise\u003cvoid\u003e {\n    console.log('执行一些释放资源的操作');\n  }\n}\n```\n\n##### 生命周期方法装饰器\n\n上面展示的 hook 是通过方法命名约定来实现生命周期 hook，我们还提供了更加可读性更强的装饰器模式。\n\n```ts\nimport {\n  LifecyclePostConstruct,\n  LifecyclePreInject,\n  LifecyclePostInject,\n  LifecycleInit,\n  LifecyclePreDestroy,\n  LifecycleDestroy,\n} from '@eggjs/tegg';\n\n@SingletonProto({\n  accessLevel: AccessLevel.PUBLIC,\n  name: 'helloInterface',\n})\nexport class HelloService {\n  @LifecyclePostConstruct()\n  protected async _postConstruct() {\n    console.log('对象构造完成');\n  }\n\n  @LifecyclePreInject()\n  protected async _preInject() {\n    console.log('依赖将要注入');\n  }\n\n  @LifecyclePostInject()\n  protected async _postInject() {\n    console.log('依赖注入完成');\n  }\n\n  @LifecycleInit()\n  protected async _init() {\n    console.log('执行一些异步的初始化过程');\n  }\n\n  @LifecyclePreDestroy()\n  protected async _preDestroy() {\n    console.log('对象将要释放了');\n  }\n\n  @LifecycleDestroy()\n  protected async _destroy() {\n    console.log('执行一些释放资源的操作');\n  }\n\n  async hello(user: User) {\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n### 注入\n\nProto 中可以依赖其他的 Proto，或者 egg 中的对象。\n\n#### 定义\n\n```typescript\n@Inject(param?: {\n  // 注入对象的名称，在某些情况下一个原型可能有多个实例\n  // 比如说 egg 的 logger\n  // 默认为属性名称\n  name?: string;\n  // 注入原型的名称\n  // 在某些情况不希望注入的原型和属性使用一个名称\n  // 默认为属性名称\n  proto?: string;\n  // 注入对象是否为可选，默认为 false\n  // 若为 false，当不存在该对象时，启动阶段将会抛出异常\n  // 若为 true，且未找到对象时，该属性值为 undefined\n  optional?: boolean;\n})\n```\n\n对于 optional 为 true 的情况，也提供了 InjectOptional 的 alias 装饰器\n```typescript\n// 等价于 @Inject({ ...params, optional: true })\n@InjectOptional(params: {\n  // 注入对象的名称，在某些情况下一个原型可能有多个实例\n  // 比如说 egg 的 logger\n  // 默认为属性名称\n  name?: string;\n  // 注入原型的名称\n  // 在某些情况不希望注入的原型和属性使用一个名称\n  // 默认为属性名称\n  proto?: string;\n})\n```\n\n#### 示例\n\n##### 简单示例\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  logger: EggLogger;\n  \n  // 等价于 @Inject({ optional: true })\n  @InjectOptional()\n  maybeUndefinedLogger?: EggLogger;\n\n  async hello(user: User): Promise\u003cstring\u003e {\n    this.logger.info(`[HelloService] hello ${user.name}`);\n    // optional inject 使用时，需要判断是否有值\n    if (this.maybeUndefinedLogger) {\n      this.maybeUndefinedLogger.info(`[HelloService] hello ${user.name}`);\n    }\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n也可在构造函数中使用 `Inject` 注解。注意 property 和 构造函数两种模式只能选一种，不能混用。\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  constructor(\n    @Inject() readonly logger: EggLogger,\n    @InjectOptional() readonly maybeUndefinedLogger?: EggLogger,\n  ) {}\n\n  async hello(user: User): Promise\u003cstring\u003e {\n    this.logger.info(`[HelloService] hello ${user.name}`);\n    // optional inject 使用时，需要判断是否有值\n    if (this.maybeUndefinedLogger) {\n      this.maybeUndefinedLogger.info(`[HelloService] hello ${user.name}`);\n    }\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n##### 复杂示例\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  // 在 config.default.js 中\n  // 配置了 customLogger，\n  // 名称为 bizLogger\n  @Inject({ name: 'bizLogger' })\n  logger: EggLogger;\n\n  async hello(user: User): Promise\u003cstring\u003e {\n    this.logger.info(`[HelloService] hello ${user.name}`);\n    const echoResponse = await this.echoAdapter.echo({ name: user.name });\n    return `hello, ${echoResponse.name}`;\n  }\n}\n```\n\n#### 限制\n\n- ContextProto 可以注入任何 Proto 但是 SingletonProto 不能注入 ContextProto\n- 原型之间不允许有循环依赖，比如 Proto A - inject -\u003e Proto B - inject- \u003e Proto A，这种是不行的\n- 类似原型之间不允许有循环依赖，module 直接也不能有循环依赖\n- 一个 module 内不能有实例化方式和名称同时相同的原型\n- **不可以注入 ctx/app，用什么注入什么**\n\n#### 兼容 egg\n\negg 中有 extend 方式，可以将对象扩展到 Context/Application 上，这些对象均可注入。Context 上的对象类比为 ContextProto，Application 上的对象类比为 SingletonProto。\n\n#### 进阶\n\n### module 内原型名称冲突\n\n一个 module 内，有两个原型，原型名相同，实例化不同，这时直接 Inject 是不行的，module 无法理解具体需要哪个对象。这时就需要告知 module 需要注入的对象实例化方式是哪种。\n\n###### 定义\n\n```typescript\n@InitTypeQualifier(initType: ObjectInitType)\n```\n\n###### 示例\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject, InitTypeQualifier, ObjectInitType } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  // 明确指定实例化方式为 CONTEXT 的 logger\n  @InitTypeQualifier(ObjectInitType.CONTEXT)\n  logger: EggLogger;\n}\n```\n\n##### module 间原型名称冲突\n\n可能多个 module 都实现了名称为 `HelloAdapter` 的原型, 且 `accessLevel = AccessLevel.PUBLIC`，需要明确的告知 module 需要注入的原型来自哪个 module.\n\n###### 定义\n\n```typescript\n@ModuleQualifier(moduleName: string)\n```\n\n###### 示例\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject, InitTypeQualifier, ObjectInitType } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  // 明确指定使用来自 foo module 的 HelloAdapter\n  @ModuleQualifier('foo')\n  helloAdapter: HelloAdapter;\n}\n```\n\n### egg 内 ctx/app 命名冲突\n\negg 内可能出现 ctx 和 app 上有同名对象的存在，我们可以通过使用 `EggQualifier` 来明确指定注入的对象来自 ctx 还是 app。不指定时，默认注入 app 上的对象。\n\n###### 定义\n\n```typescript\n@EggQualifier(eggType: EggType)\n```\n\n###### 示例\n\n```typescript\nimport { EggLogger } from 'egg';\nimport { Inject, EggQualifier, EggType } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  // 明确指定注入 ctx 上的 foo 而不是 app 上的 foo\n  @EggQualifier(EggType.CONTEXT)\n  foo: Foo;\n}\n```\n\n### 单测\n\n#### 单测 Context\n\n在单测中需要获取 egg Context 时，可以使用以下 API。\n\n```typescript\nexport interface Application {\n  /**\n   * 创建 module 上下文 scope\n   */\n  mockModuleContextScope\u003cR=any\u003e(this: MockApplication, fn: (ctx: Context) =\u003e Promise\u003cR\u003e, data?: any): Promise\u003cR\u003e;\n}\n```\n\n#### 获取对象实例\n\n在单测中需要获取 module 中的对象实例时，可以使用以下 API。\n\n```typescript\nexport interface Application {\n  /**\n   * 通过一个类来获取实例\n   */\n  getEggObject\u003cT\u003e (clazz: EggProtoImplClass\u003cT\u003e ): Promise \u003cT\u003e;\n  /**\n   * 通过对象名称来获取实例\n   */\n  getEggObjectFromName\u003cT\u003e(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise\u003cunknown\u003e;\n}\n\nexport interface Context {\n  /**\n   * 通过一个类来获取实例\n   */\n  getEggObject\u003cT\u003e (clazz: EggProtoImplClass\u003cT\u003e ): Promise \u003cT\u003e;\n  /**\n   * 通过对象名称来获取实例\n   */\n  getEggObjectFromName\u003cT\u003e(name: string, qualifiers?: QualifierInfo | QualifierInfo[]): Promise\u003cunknown\u003e;\n}\n```\n\n### Egg 兼容性\n\n目前 module 尚未实现所有 egg 的特性，如果需要使用 egg 的功能，可以通过一些方式来兼容。\n\n##### Schedule\n\n目前 Schedule 尚未实现注解，仍然需要使用 egg 的目录和继承方式，在这种场景下如果需要使用 module 的实现，需要使用 `ctx.beginModuleScope`。举个例子：\n\n```typescript\n// notify/EC_FOO.js\n\nimport { Subscription, Context } from 'egg;\n\nclass FooSubscriber extends Subscription {\n  private readonly ctx: Context;\n\n  constructor(ctx: Context) {\n    super(ctx);\n  }\n\n  async subscribe(msg) {\n    await ctx.beginModuleScope(async () =\u003e {\n      await ctx.module.fooService.hello(msg);\n    });\n  }\n}\n\nmodule.exports = Subscription;\n\n```\n\n#### 注入 egg 对象\n\nmodule 会自动去遍历 egg 的 Application 和 Context 对象，获取其所有的属性，所有的属性都可以进行无缝的注入。举个例子，如何注入现在的 egg proxy:\n\n```typescript\nimport { IProxy } from 'egg'\n\n@ContextProto()\nclass FooService {\n  @Inject()\n  private readonly proxy: IProxy;\n\n  get fooFacade() {\n    return this.proxy.fooFacade;\n  }\n}\n```\n\n##### 注入 logger\n\n专为 logger 做了优化，可以直接注入 custom logger。\n\n举个例子:\n\n有一个自定义的 fooLogger\n\n```javascript\n// config.default.js\nexports.customLogger = {\n  fooLogger: {\n    file: 'foo.log',\n  },\n};\n```\n\n代码中可以直接注入:\n\n```typescript\nimport { EggLogger } from 'egg';\n\nclass FooService {\n  @Inject()\n  private fooLogger: EggLogger;\n}\n```\n\n#### 注入 egg 方法\n\n由于 module 注入时，只可以注入对象，不能注入方法，如果需要使用现有 egg 的方法，就需要对方法进行一定的封装。\n\n举个例子：假设 context 上有一个方法是 `getHeader`，在 module 中需要使用这个方法需要进行封装。\n\n```typescript\n// extend/context.ts\n\nexport default {\n  getHeader() {\n    return '23333';\n  }\n}\n\n```\n\n先将方法封装成一个对象。\n\n```typescript\n// HeaderHelper.ts\n\nclass HeaderHelper {\n  constructor(ctx) {\n    this.ctx = ctx;\n  }\n\n  getHeader(): string {\n    return this.ctx.getHeader();\n  }\n}\n```\n\n再将对象放到 Context 扩展上即可。\n\n```typescript\n// extend/context.ts\n\nconst HEADER_HELPER = Symbol('context#headerHelper');\n\nexport default {\n  get headerHelper() {\n    if (!this[HEADER_HELPER]) {\n      this[HEADER_HELPER] = new HeaderHelper(this);\n    }\n    return this[HEADER_HELPER];\n  }\n}\n\n```\n\n### 生命周期\n\n在 module 中，每个对象实例都有自己的生命周期，开发者可以对每个对象进行细致的控制。只要为对象实现 module 定义好的接口即可。所有生命周期 hook 均为可选方法，不需要的可以不实现。\n\n#### 接口定义\n\n```typescript\ninterface EggObjectLifecycle {\n  /**\n   * 在对象的构造函数执行完成之后执行\n   */\n  async postConstruct?();\n\n  /**\n   * 在注入对象依赖之前执行\n   */\n  async preInject?();\n\n  /**\n   * 在注入对象依赖之后执行\n   */\n  async postInject?();\n\n  /**\n   * 执行对象自定义异步初始化函数\n   */\n  async init?();\n\n  /**\n   * 在对象释放前执行\n   */\n  async preDestroy?();\n\n  /**\n   * 释放对象依赖的底层资源\n   */\n  async destroy?();\n}\n```\n\n#### 实现\n\n```typescript\nimport { EggObjectLifecycle } from '@eggjs/tegg';\n\n@SingletonProto()\nclass FooService implement EggObjectLifecycle {\n  @Inject()\n  cacheService: CacheService;\n\n  cache: Record\u003cstring, string\u003e;\n\n  async init() {\n    this.cache = await this.cacheService.get(key);\n  }\n}\n```\n\n### 异步任务\n\nmodule 在请求结束后会把请求相关的对象释放，所以在请求中使用 `process.nextTick`、 `setTimeout`、 `setInterval`这类接口并不安全，可能导致一些错误。因此需要使用框架提供的接口，以便框架了解当前请求有哪些异步任务在执行，等异步任务执行完成后再释放对象。\n\n#### 安装\n\n```shell\nnpm i --save @eggjs/tegg-background-task\n```\n\n#### 使用\n\n```typescript\nimport { BackgroundTaskHelper } from '@eggjs/tegg-background-task';\n\n@ContextProto()\nexport default class BackgroundService {\n  @Inject()\n  private readonly backgroundTaskHelper: BackgroundTaskHelper;\n\n  async backgroundAdd() {\n    this.backgroundTaskHelper.run(async () =\u003e {\n      // do the background task\n    });\n  }\n}\n```\n\n#### 超时时间\n\n框架不会无限的等待异步任务执行，在默认 5s 之后如果异步任务还没有完成则会放弃等待开始执行释放过程。如果需要等待更长的时间，建议有两种方式：\n\n- **推荐方式：将异步任务转发给单例对象（SingletonProto）来执行，单例对象永远不会释放**\n- 调整超时时间，对 `backgroundTaskHelper.timeout` 进行赋值即可\n- 如果将超时时间设置为 `Infinity`，框架将不会超时\n- 可以在 config 文件中指定 `backgroundTask.timeout` 来全局覆盖默认的超时时间\n\n### 动态注入\n\n#### 使用\n\n定义一个抽象类和一个类型枚举。\n\n```ts\nexport enum HelloType {\n  FOO = 'FOO',\n  BAR = 'BAR',\n}\n\nexport abstract class AbstractHello {\n  abstract hello(): string;\n}\n```\n\n定义一个自定义枚举。\n\n```ts\nimport { ImplDecorator, QualifierImplDecoratorUtil } from '@eggjs/tegg';\nimport { ContextHelloType } from '../FooType';\nimport { AbstractContextHello } from '../AbstractHello';\n\nexport const HELLO_ATTRIBUTE = 'HELLO_ATTRIBUTE';\n\nexport const Hello: ImplDecorator\u003cAbstractHello, typeof HelloType\u003e =\n  QualifierImplDecoratorUtil.generatorDecorator(AbstractHello, HELLO_ATTRIBUTE);\n\n\n```\n\n实现抽象类。\n\n```ts\nimport { ContextProto } from '@eggjs/tegg';\nimport { ContextHello } from '../decorator/Hello';\nimport { ContextHelloType } from '../FooType';\nimport { AbstractContextHello } from '../AbstractHello';\n\n@ContextProto()\n@Hello(HelloType.BAR)\nexport class BarHello extends AbstractHello {\n  hello(): string {\n    return `hello, bar`;\n  }\n}\n\n```\n\n动态获取实现。\n\n```ts\nimport { EggObjectFactory } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise\u003cstring\u003e {\n    const helloImpl = await this.eggObjectFactory.getEggObject(AbstractHello, HelloType.BAR);\n    return helloImpl.hello();\n  }\n}\n```\n\n动态获取多个实现，通过 for/await 循环获得实例。\n\n```ts\nimport { EggObjectFactory } from '@eggjs/tegg';\n\n@ContextProto()\nexport class HelloService {\n  @Inject()\n  private readonly eggObjectFactory: EggObjectFactory;\n\n  async hello(): Promise\u003cstring[]\u003e {\n    const helloImpls = await this.eggObjectFactory.getEggObjects(AbstractHello);\n    const messages = [];\n    for await (const helloImpl of helloImpls) {\n      messages.push(helloImpl.hello());\n    }\n    return messages;\n  }\n}\n```\n\n### 配置项目 module\n\n一般情况下，无需在项目中手动声明包含了哪些 module，tegg 会自动在项目目录下进行扫描。但是会存在一些特殊的情况，tegg 无法扫描到，比如说 module 是通过 npm 包的方式来发布。因此 tegg 支持通过 `config/module.json` 的方式来声明包含了哪些 module。\n\n支持通过 `path` 引用 `app/modules/foo` 目录下的 module。\n\n```json\n[\n  {\"path\":  \"../app/modules/foo\"}\n]\n```\n\n支持通过 `package` 引用使用 npm 发布的 module。\n\n```json\n[\n  {\"package\": \"foo\"}\n]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feggjs%2Ftegg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feggjs%2Ftegg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feggjs%2Ftegg/lists"}